diff --git a/internal/ast/ast.go b/internal/ast/ast.go
index 5b1a6fb905..7b83f275f3 100644
--- a/internal/ast/ast.go
+++ b/internal/ast/ast.go
@@ -749,6 +749,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 {
@@ -1655,6 +1667,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
@@ -1673,6 +1689,7 @@ type (
CatchClauseNode = Node
CaseBlockNode = Node
CaseOrDefaultClauseNode = Node
+ CaseClauseNode = Node
VariableDeclarationNode = Node
VariableDeclarationListNode = Node
BindingElementNode = Node
@@ -1695,6 +1712,7 @@ type (
JsxOpeningFragmentNode = Node
JsxClosingFragmentNode = Node
SourceFileNode = Node
+ PropertyAccessExpressionNode = Node
)
type (
@@ -8219,6 +8237,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 {
@@ -9446,7 +9468,7 @@ func (node *JSDocThisTag) Clone(f NodeFactoryCoercible) *Node {
type JSDocImportTag struct {
JSDocTagBase
ImportClause *Declaration
- ModuleSpecifier *Node
+ ModuleSpecifier *Expression
Attributes *Node
}
@@ -9947,7 +9969,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
}
diff --git a/internal/ast/nodeflags.go b/internal/ast/nodeflags.go
index 2feb4f2a52..5b6441ed58 100644
--- a/internal/ast/nodeflags.go
+++ b/internal/ast/nodeflags.go
@@ -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)
diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go
index 9f6c560b98..7f027b5aa3 100644
--- a/internal/ast/utilities.go
+++ b/internal/ast/utilities.go
@@ -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,
@@ -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 {
@@ -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 {
@@ -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 ||
@@ -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
+}
diff --git a/internal/astnav/tokens.go b/internal/astnav/tokens.go
index 6bccfe0f16..49bc960a0c 100644
--- a/internal/astnav/tokens.go
+++ b/internal/astnav/tokens.go
@@ -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 {
@@ -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)
}
@@ -433,7 +433,7 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN
shouldVisitNode := func(node *ast.Node) bool {
// Node is synthetic or out of the desired range: don't visit it.
return !(node.Flags&ast.NodeFlagsReparsed != 0 ||
- node.End() > endPos || getStartOfNode(node, sourceFile, !excludeJSDoc /*includeJSDoc*/) >= position)
+ node.End() > endPos || GetStartOfNode(node, sourceFile, !excludeJSDoc /*includeJSDoc*/) >= position)
}
visitNode := func(node *ast.Node, _ *ast.NodeVisitor) *ast.Node {
if node == nil {
@@ -560,3 +560,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
+}
diff --git a/internal/checker/checker.go b/internal/checker/checker.go
index e0501bca70..8dee49403e 100644
--- a/internal/checker/checker.go
+++ b/internal/checker/checker.go
@@ -823,6 +823,7 @@ type Checker struct {
emitResolverOnce sync.Once
_jsxNamespace string
_jsxFactoryEntity *ast.Node
+ skipDirectInferenceNodes core.Set[*ast.Node]
}
func NewChecker(program Program) *Checker {
@@ -1156,7 +1157,7 @@ func (c *Checker) getGlobalSymbol(name string, meaning ast.SymbolFlags, diagnost
func (c *Checker) initializeClosures() {
c.isPrimitiveOrObjectOrEmptyType = func(t *Type) bool {
- return t.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.isEmptyAnonymousObjectType(t)
+ return t.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.IsEmptyAnonymousObjectType(t)
}
c.containsMissingType = func(t *Type) bool {
return t == c.missingType || t.flags&TypeFlagsUnion != 0 && t.Types()[0] == c.missingType
@@ -1406,7 +1407,7 @@ func (c *Checker) checkAndReportErrorForInvalidInitializer(errorLocation *ast.No
}
func (c *Checker) checkAndReportErrorForMissingPrefix(errorLocation *ast.Node, name string) bool {
- if !ast.IsIdentifier(errorLocation) || errorLocation.Text() != name || isTypeReferenceIdentifier(errorLocation) || isInTypeQuery(errorLocation) {
+ if !ast.IsIdentifier(errorLocation) || errorLocation.Text() != name || isTypeReferenceIdentifier(errorLocation) || IsInTypeQuery(errorLocation) {
return false
}
container := c.getThisContainer(errorLocation, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/)
@@ -1718,7 +1719,7 @@ func (c *Checker) onSuccessfullyResolvedSymbol(errorLocation *ast.Node, result *
importDecl := core.Find(nonValueSymbol.Declarations, func(d *ast.Node) bool {
return ast.NodeKindIs(d, ast.KindImportSpecifier, ast.KindImportClause, ast.KindNamespaceImport, ast.KindImportEqualsDeclaration)
})
- if importDecl != nil && !isTypeOnlyImportDeclaration(importDecl) {
+ if importDecl != nil && !ast.IsTypeOnlyImportDeclaration(importDecl) {
c.error(importDecl, diagnostics.Import_0_conflicts_with_global_value_used_in_this_file_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, name)
}
}
@@ -1764,7 +1765,7 @@ func (c *Checker) isBlockScopedNameDeclaredBeforeUse(declaration *ast.Node, usag
useFile := ast.GetSourceFileOfNode(usage)
declContainer := ast.GetEnclosingBlockScopeContainer(declaration)
if declarationFile != useFile {
- if (c.moduleKind != core.ModuleKindNone && (declarationFile.ExternalModuleIndicator != nil || useFile.ExternalModuleIndicator != nil)) || c.compilerOptions.OutFile == "" || isInTypeQuery(usage) || declaration.Flags&ast.NodeFlagsAmbient != 0 {
+ if (c.moduleKind != core.ModuleKindNone && (declarationFile.ExternalModuleIndicator != nil || useFile.ExternalModuleIndicator != nil)) || c.compilerOptions.OutFile == "" || IsInTypeQuery(usage) || declaration.Flags&ast.NodeFlagsAmbient != 0 {
// nodes are in different files and order cannot be determined
return true
}
@@ -1777,7 +1778,7 @@ func (c *Checker) isBlockScopedNameDeclaredBeforeUse(declaration *ast.Node, usag
return slices.Index(sourceFiles, declarationFile) <= slices.Index(sourceFiles, useFile)
}
// deferred usage in a type context is always OK regardless of the usage position:
- if usage.Flags&ast.NodeFlagsJSDoc != 0 || isInTypeQuery(usage) || c.isInAmbientOrTypeNode(usage) {
+ if usage.Flags&ast.NodeFlagsJSDoc != 0 || IsInTypeQuery(usage) || c.isInAmbientOrTypeNode(usage) {
return true
}
if declaration.Pos() <= usage.Pos() && !(ast.IsPropertyDeclaration(declaration) && isThisProperty(usage.Parent) && declaration.Initializer() == nil && !isExclamationToken(declaration.AsPropertyDeclaration().PostfixToken)) {
@@ -4134,7 +4135,7 @@ func (c *Checker) checkBaseTypeAccessibility(t *Type, node *ast.Node) {
if len(signatures) != 0 {
declaration := signatures[0].declaration
if declaration != nil && hasModifier(declaration, ast.ModifierFlagsPrivate) {
- typeClassDeclaration := getClassLikeDeclarationOfSymbol(t.symbol)
+ typeClassDeclaration := ast.GetClassLikeDeclarationOfSymbol(t.symbol)
if !c.isNodeWithinClass(node, typeClassDeclaration) {
c.error(node, diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, c.getFullyQualifiedName(t.symbol, nil))
}
@@ -4227,7 +4228,7 @@ basePropertyCheck:
// It is an error to inherit an abstract member without implementing it or being declared abstract.
// If there is no declaration for the derived class (as in the case of class expressions),
// then the class cannot be declared abstract.
- derivedClassDecl := getClassLikeDeclarationOfSymbol(t.symbol)
+ derivedClassDecl := ast.GetClassLikeDeclarationOfSymbol(t.symbol)
if derivedClassDecl == nil || !ast.HasSyntacticModifier(derivedClassDecl, ast.ModifierFlagsAbstract) {
// Searches other base types for a declaration that would satisfy the inherited abstract member.
// (The class may have more than one base type via declaration merging with an interface with the
@@ -4288,7 +4289,7 @@ basePropertyCheck:
if uninitialized != nil && derived.Flags&ast.SymbolFlagsTransient == 0 && baseDeclarationFlags&ast.ModifierFlagsAbstract == 0 && derivedDeclarationFlags&ast.ModifierFlagsAbstract == 0 && !core.Some(derived.Declarations, func(d *ast.Node) bool {
return d.Flags&ast.NodeFlagsAmbient != 0
}) {
- constructor := ast.FindConstructorDeclaration(getClassLikeDeclarationOfSymbol(t.symbol))
+ constructor := ast.FindConstructorDeclaration(ast.GetClassLikeDeclarationOfSymbol(t.symbol))
propName := uninitialized.Name()
if isExclamationToken(uninitialized.AsPropertyDeclaration().PostfixToken) || constructor == nil || !ast.IsIdentifier(propName) || !c.strictNullChecks || !c.isPropertyInitializedInConstructor(propName, t, constructor) {
errorMessage := diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration
@@ -4824,7 +4825,7 @@ func (c *Checker) checkModuleDeclaration(node *ast.Node) {
}
}
if isAmbientExternalModule {
- if isExternalModuleAugmentation(node) {
+ if ast.IsExternalModuleAugmentation(node) {
// body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module)
// otherwise we'll be swamped in cascading errors.
// We can detect if augmentation was applied using following rules:
@@ -5027,7 +5028,7 @@ func (c *Checker) checkImportAttributes(declaration *ast.Node) {
if importAttributesType != c.emptyObjectType {
c.checkTypeAssignableTo(c.getTypeFromImportAttributes(node), c.getNullableType(importAttributesType, TypeFlagsUndefined), node, nil)
}
- isTypeOnly := isTypeOnlyImportOrExportDeclaration(declaration)
+ isTypeOnly := ast.IsTypeOnlyImportOrExportDeclaration(declaration)
override := c.getResolutionModeOverride(node.AsImportAttributes(), isTypeOnly)
isImportAttributes := node.AsImportAttributes().Token == ast.KindWithKeyword
if isTypeOnly && override != core.ResolutionModeNone {
@@ -6276,12 +6277,12 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
} else if !ast.IsExportSpecifier(node) {
// Look at 'compilerOptions.isolatedModules' and not 'getIsolatedModules(...)' (which considers 'verbatimModuleSyntax')
// here because 'verbatimModuleSyntax' will already have an error for importing a type without 'import type'.
- appearsValueyToTranspiler := c.compilerOptions.IsolatedModules.IsTrue() && ast.FindAncestor(node, isTypeOnlyImportOrExportDeclaration) == nil
+ appearsValueyToTranspiler := c.compilerOptions.IsolatedModules.IsTrue() && ast.FindAncestor(node, ast.IsTypeOnlyImportOrExportDeclaration) == nil
if appearsValueyToTranspiler && symbol.Flags&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue) != 0 {
c.error(node, diagnostics.Import_0_conflicts_with_local_value_so_must_be_declared_with_a_type_only_import_when_isolatedModules_is_enabled, c.symbolToString(symbol), c.getIsolatedModulesLikeFlagName())
}
}
- if c.compilerOptions.GetIsolatedModules() && !isTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 {
+ if c.compilerOptions.GetIsolatedModules() && !ast.IsTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 {
typeOnlyAlias := c.getTypeOnlyAliasDeclaration(symbol)
isType := targetFlags&ast.SymbolFlagsValue == 0
if isType || typeOnlyAlias != nil {
@@ -6330,7 +6331,7 @@ func (c *Checker) checkAliasSymbol(node *ast.Node) {
c.error(node, diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_module_is_set_to_preserve)
}
// !!!
- // if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !isTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 && targetFlags&ast.SymbolFlagsConstEnum != 0 {
+ // if c.compilerOptions.VerbatimModuleSyntax.IsTrue() && !ast.IsTypeOnlyImportOrExportDeclaration(node) && node.Flags&ast.NodeFlagsAmbient == 0 && targetFlags&ast.SymbolFlagsConstEnum != 0 {
// constEnumDeclaration := target.ValueDeclaration
// redirect := host.getRedirectReferenceForResolutionFromSourceOfProject(ast.GetSourceFileOfNode(constEnumDeclaration).ResolvedPath)
// if constEnumDeclaration.Flags&ast.NodeFlagsAmbient != 0 && (redirect == nil || !shouldPreserveConstEnums(redirect.commandLine.options)) {
@@ -6887,7 +6888,7 @@ func (c *Checker) checkNonNullTypeWithReporter(t *Type, node *ast.Node, reportEr
facts := c.getTypeFacts(t, TypeFactsIsUndefinedOrNull)
if facts&TypeFactsIsUndefinedOrNull != 0 {
reportError(c, node, facts)
- nonNullable := c.getNonNullableType(t)
+ nonNullable := c.GetNonNullableType(t)
if nonNullable.flags&(TypeFlagsNullable|TypeFlagsNever) != 0 {
return c.errorType
}
@@ -7070,7 +7071,7 @@ func (c *Checker) instantiateTypeWithSingleGenericCallSignature(node *ast.Node,
if contextualType == nil {
return t
}
- contextualSignature := c.getSingleSignature(c.getNonNullableType(contextualType), core.IfElse(callSignature != nil, SignatureKindCall, SignatureKindConstruct), false /*allowMembers*/)
+ contextualSignature := c.getSingleSignature(c.GetNonNullableType(contextualType), core.IfElse(callSignature != nil, SignatureKindCall, SignatureKindConstruct), false /*allowMembers*/)
if contextualSignature == nil || len(contextualSignature.typeParameters) != 0 {
return t
}
@@ -8136,7 +8137,7 @@ func (c *Checker) resolveNewExpression(node *ast.Node, candidatesOutArray *[]*Si
return c.resolveErrorCall(node)
}
if expressionType.symbol != nil {
- valueDecl := getClassLikeDeclarationOfSymbol(expressionType.symbol)
+ valueDecl := ast.GetClassLikeDeclarationOfSymbol(expressionType.symbol)
if valueDecl != nil && hasModifier(valueDecl, ast.ModifierFlagsAbstract) {
c.error(node, diagnostics.Cannot_create_an_instance_of_an_abstract_class)
return c.resolveErrorCall(node)
@@ -8175,7 +8176,7 @@ func (c *Checker) isConstructorAccessible(node *ast.Node, signature *Signature)
if modifiers == 0 || !ast.IsConstructorDeclaration(declaration) {
return true
}
- declaringClassDeclaration := getClassLikeDeclarationOfSymbol(declaration.Parent.Symbol())
+ declaringClassDeclaration := ast.GetClassLikeDeclarationOfSymbol(declaration.Parent.Symbol())
declaringClass := c.getDeclaredTypeOfSymbol(declaration.Parent.Symbol())
// A private or protected constructor can only be instantiated within its own class (or a subclass, for protected)
if !c.isNodeWithinClass(node, declaringClassDeclaration) {
@@ -8907,7 +8908,7 @@ func (c *Checker) getThisArgumentType(node *ast.Node) *Type {
thisArgumentType := c.checkExpression(node)
switch {
case ast.IsOptionalChainRoot(node.Parent):
- return c.getNonNullableType(thisArgumentType)
+ return c.GetNonNullableType(thisArgumentType)
case ast.IsOptionalChain(node.Parent):
return c.removeOptionalTypeMarker(thisArgumentType)
}
@@ -9939,7 +9940,7 @@ func (c *Checker) needCollisionCheckForIdentifier(node *ast.Node, identifier *as
}
if ast.IsImportClause(node) || ast.IsImportEqualsDeclaration(node) || ast.IsImportSpecifier(node) {
// type-only imports do not require collision checks against runtime values.
- if isTypeOnlyImportOrExportDeclaration(node) {
+ if ast.IsTypeOnlyImportOrExportDeclaration(node) {
return false
}
}
@@ -9960,13 +9961,13 @@ func (c *Checker) checkNonNullAssertion(node *ast.Node) *Type {
if node.Flags&ast.NodeFlagsOptionalChain != 0 {
return c.checkNonNullChain(node)
}
- return c.getNonNullableType(c.checkExpression(node.Expression()))
+ return c.GetNonNullableType(c.checkExpression(node.Expression()))
}
func (c *Checker) checkNonNullChain(node *ast.Node) *Type {
leftType := c.checkExpression(node.Expression())
nonOptionalType := c.getOptionalExpressionType(leftType, node.Expression())
- return c.propagateOptionalTypeMarker(c.getNonNullableType(nonOptionalType), node, nonOptionalType != leftType)
+ return c.propagateOptionalTypeMarker(c.GetNonNullableType(nonOptionalType), node, nonOptionalType != leftType)
}
func (c *Checker) checkExpressionWithTypeArguments(node *ast.Node) *Type {
@@ -10366,7 +10367,7 @@ func (c *Checker) checkSyntheticExpression(node *ast.Node) *Type {
}
func (c *Checker) checkIdentifier(node *ast.Node, checkMode CheckMode) *Type {
- if isThisInTypeQuery(node) {
+ if ast.IsThisInTypeQuery(node) {
return c.checkThisExpression(node)
}
symbol := c.getResolvedSymbol(node)
@@ -10479,7 +10480,7 @@ func (c *Checker) checkIdentifier(node *ast.Node, checkMode CheckMode) *Type {
isSpreadDestructuringAssignmentTarget ||
isModuleExports ||
c.isSameScopedBindingElement(node, declaration) ||
- t != c.autoType && t != c.autoArrayType && (!c.strictNullChecks || t.flags&(TypeFlagsAnyOrUnknown|TypeFlagsVoid) != 0 || isInTypeQuery(node) || c.isInAmbientOrTypeNode(node) || node.Parent.Kind == ast.KindExportSpecifier) ||
+ t != c.autoType && t != c.autoArrayType && (!c.strictNullChecks || t.flags&(TypeFlagsAnyOrUnknown|TypeFlagsVoid) != 0 || IsInTypeQuery(node) || c.isInAmbientOrTypeNode(node) || node.Parent.Kind == ast.KindExportSpecifier) ||
ast.IsNonNullExpression(node.Parent) ||
ast.IsVariableDeclaration(declaration) && declaration.AsVariableDeclaration().ExclamationToken != nil ||
declaration.Flags&ast.NodeFlagsAmbient != 0
@@ -10498,7 +10499,7 @@ func (c *Checker) checkIdentifier(node *ast.Node, checkMode CheckMode) *Type {
}
var flowType *Type
if isAutomaticTypeInNonNull {
- flowType = c.getNonNullableType(c.getFlowTypeOfReferenceEx(node, t, initialType, flowContainer, nil))
+ flowType = c.GetNonNullableType(c.getFlowTypeOfReferenceEx(node, t, initialType, flowContainer, nil))
} else {
flowType = c.getFlowTypeOfReferenceEx(node, t, initialType, flowContainer, nil)
}
@@ -10671,7 +10672,7 @@ func (c *Checker) checkPropertyAccessExpressionOrQualifiedName(node *ast.Node, l
if c.compilerOptions.NoPropertyAccessFromIndexSignature == core.TSTrue && ast.IsPropertyAccessExpression(node) {
c.error(right, diagnostics.Property_0_comes_from_an_index_signature_so_it_must_be_accessed_with_0, right.Text())
}
- if indexInfo.declaration != nil && c.isDeprecatedDeclaration(indexInfo.declaration) {
+ if indexInfo.declaration != nil && c.IsDeprecatedDeclaration(indexInfo.declaration) {
c.addDeprecatedSuggestion(right, []*ast.Node{indexInfo.declaration}, right.Text())
}
} else {
@@ -10847,7 +10848,7 @@ func (c *Checker) reportNonexistentProperty(propNode *ast.Node, containingType *
typeName := c.TypeToString(containingType)
diagnostic = NewDiagnosticChainForNode(diagnostic, propNode, diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_to_access_the_static_member_2_instead, propName, typeName, typeName+"."+propName)
} else {
- promisedType := c.getPromisedTypeOfPromise(containingType)
+ promisedType := c.GetPromisedTypeOfPromise(containingType)
if promisedType != nil && c.getPropertyOfType(promisedType, propNode.Text()) != nil {
diagnostic = NewDiagnosticChainForNode(diagnostic, propNode, diagnostics.Property_0_does_not_exist_on_type_1, scanner.DeclarationNameToString(propNode), c.TypeToString(containingType))
diagnostic.AddRelatedInfo(NewDiagnosticForNode(propNode, diagnostics.Did_you_forget_to_use_await))
@@ -10987,11 +10988,11 @@ func (c *Checker) isUncalledFunctionReference(node *ast.Node, symbol *ast.Symbol
if parent == nil {
parent = node.Parent
}
- if isCallLikeExpression(parent) {
+ if ast.IsCallLikeExpression(parent) {
return isCallOrNewExpression(parent) && ast.IsIdentifier(node) && c.hasMatchingArgument(parent, node)
}
return core.Every(symbol.Declarations, func(d *ast.Node) bool {
- return !ast.IsFunctionLike(d) || c.isDeprecatedDeclaration(d)
+ return !ast.IsFunctionLike(d) || c.IsDeprecatedDeclaration(d)
})
}
return true
@@ -11123,7 +11124,7 @@ func (c *Checker) checkPropertyAccessibilityAtLocation(location *ast.Node, isSup
if flags&ast.ModifierFlagsAbstract != 0 && c.symbolHasNonMethodDeclaration(prop) && (isThisProperty(location) ||
isThisInitializedObjectBindingExpression(location) ||
ast.IsObjectBindingPattern(location.Parent) && isThisInitializedDeclaration(location.Parent.Parent)) {
- declaringClassDeclaration := getClassLikeDeclarationOfSymbol(c.getParentOfSymbol(prop))
+ declaringClassDeclaration := ast.GetClassLikeDeclarationOfSymbol(c.getParentOfSymbol(prop))
if declaringClassDeclaration != nil && c.isNodeUsedDuringClassInitialization(location) {
if errorNode != nil {
c.error(errorNode, diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, c.symbolToString(prop), declaringClassDeclaration.Name().Text())
@@ -11138,7 +11139,7 @@ func (c *Checker) checkPropertyAccessibilityAtLocation(location *ast.Node, isSup
// Property is known to be private or protected at this point
// Private property is accessible if the property is within the declaring class
if flags&ast.ModifierFlagsPrivate != 0 {
- declaringClassDeclaration := getClassLikeDeclarationOfSymbol(c.getParentOfSymbol(prop))
+ declaringClassDeclaration := ast.GetClassLikeDeclarationOfSymbol(c.getParentOfSymbol(prop))
if !c.isNodeWithinClass(location, declaringClassDeclaration) {
if errorNode != nil {
c.error(errorNode, diagnostics.Property_0_is_private_and_only_accessible_within_class_1, c.symbolToString(prop), c.TypeToString(c.getDeclaringClass(prop)))
@@ -11353,7 +11354,7 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type {
// for 'this' is the non-null form of the contextual type for the containing object literal or
// the type of the object literal itself.
if contextualType != nil {
- thisType = c.getNonNullableType(contextualType)
+ thisType = c.GetNonNullableType(contextualType)
} else {
thisType = c.checkExpressionCached(containingLiteral)
}
@@ -11407,7 +11408,7 @@ func (c *Checker) checkThisExpression(node *ast.Node) *Type {
// do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks
}
}
- t := c.tryGetThisTypeAtEx(node, true /*includeGlobalThis*/, container)
+ t := c.TryGetThisTypeAtEx(node, true /*includeGlobalThis*/, container)
if c.noImplicitThis {
globalThisType := c.getTypeOfSymbol(c.globalThisSymbol)
if t == globalThisType && capturedByArrowFunction {
@@ -11430,10 +11431,13 @@ func (c *Checker) checkThisExpression(node *ast.Node) *Type {
}
func (c *Checker) tryGetThisTypeAt(node *ast.Node) *Type {
- return c.tryGetThisTypeAtEx(node, true /*includeGlobalThis*/, c.getThisContainer(node, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/))
+ return c.TryGetThisTypeAtEx(node, true /*includeGlobalThis*/, nil /*container*/)
}
-func (c *Checker) tryGetThisTypeAtEx(node *ast.Node, includeGlobalThis bool, container *ast.Node) *Type {
+func (c *Checker) TryGetThisTypeAtEx(node *ast.Node, includeGlobalThis bool, container *ast.Node) *Type {
+ if container == nil {
+ container = c.getThisContainer(node, false /*includeArrowFunctions*/, false /*includeClassComputedPropertyName*/)
+ }
if ast.IsFunctionLike(container) && (!c.isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container) != nil) {
thisType := c.getThisTypeOfDeclaration(container)
// Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated.
@@ -11777,7 +11781,7 @@ func (c *Checker) checkBinaryLikeExpression(left *ast.Node, operatorToken *ast.N
case ast.KindBarBarToken, ast.KindBarBarEqualsToken:
resultType := leftType
if c.hasTypeFacts(leftType, TypeFactsFalsy) {
- resultType = c.getUnionTypeEx([]*Type{c.getNonNullableType(c.removeDefinitelyFalsyTypes(leftType)), rightType}, UnionReductionSubtype, nil, nil)
+ resultType = c.getUnionTypeEx([]*Type{c.GetNonNullableType(c.removeDefinitelyFalsyTypes(leftType)), rightType}, UnionReductionSubtype, nil, nil)
}
if operator == ast.KindBarBarEqualsToken {
c.checkAssignmentOperator(left, operator, right, leftType, rightType)
@@ -11789,7 +11793,7 @@ func (c *Checker) checkBinaryLikeExpression(left *ast.Node, operatorToken *ast.N
}
resultType := leftType
if c.hasTypeFacts(leftType, TypeFactsEQUndefinedOrNull) {
- resultType = c.getUnionTypeEx([]*Type{c.getNonNullableType(leftType), rightType}, UnionReductionSubtype, nil, nil)
+ resultType = c.getUnionTypeEx([]*Type{c.GetNonNullableType(leftType), rightType}, UnionReductionSubtype, nil, nil)
}
if operator == ast.KindQuestionQuestionEqualsToken {
c.checkAssignmentOperator(left, operator, right, leftType, rightType)
@@ -12335,7 +12339,7 @@ func (c *Checker) checkInExpression(left *ast.Expression, right *ast.Expression,
func (c *Checker) hasEmptyObjectIntersection(t *Type) bool {
return someType(t, func(t *Type) bool {
- return t == c.unknownEmptyObjectType || t.flags&TypeFlagsIntersection != 0 && c.isEmptyAnonymousObjectType(c.getBaseConstraintOrType(t))
+ return t == c.unknownEmptyObjectType || t.flags&TypeFlagsIntersection != 0 && c.IsEmptyAnonymousObjectType(c.getBaseConstraintOrType(t))
})
}
@@ -13142,7 +13146,7 @@ func (c *Checker) addErrorOrSuggestion(isError bool, diagnostic *ast.Diagnostic)
}
}
-func (c *Checker) isDeprecatedDeclaration(declaration *ast.Node) bool {
+func (c *Checker) IsDeprecatedDeclaration(declaration *ast.Node) bool {
return c.getCombinedNodeFlagsCached(declaration)&ast.NodeFlagsDeprecated != 0
}
@@ -13167,12 +13171,12 @@ func (c *Checker) isDeprecatedSymbol(symbol *ast.Symbol) bool {
parentSymbol := c.getParentOfSymbol(symbol)
if parentSymbol != nil && len(symbol.Declarations) > 1 {
if parentSymbol.Flags&ast.SymbolFlagsInterface != 0 {
- return core.Some(symbol.Declarations, c.isDeprecatedDeclaration)
+ return core.Some(symbol.Declarations, c.IsDeprecatedDeclaration)
} else {
- return core.Every(symbol.Declarations, c.isDeprecatedDeclaration)
+ return core.Every(symbol.Declarations, c.IsDeprecatedDeclaration)
}
}
- return symbol.ValueDeclaration != nil && c.isDeprecatedDeclaration(symbol.ValueDeclaration) || len(symbol.Declarations) != 0 && core.Every(symbol.Declarations, c.isDeprecatedDeclaration)
+ return symbol.ValueDeclaration != nil && c.IsDeprecatedDeclaration(symbol.ValueDeclaration) || len(symbol.Declarations) != 0 && core.Every(symbol.Declarations, c.IsDeprecatedDeclaration)
}
func (c *Checker) hasParseDiagnostics(sourceFile *ast.SourceFile) bool {
@@ -13576,7 +13580,7 @@ func (c *Checker) getTargetOfImportEqualsDeclaration(node *ast.Node, dontResolve
commonJSPropertyAccess := c.getCommonJSPropertyAccess(node)
if commonJSPropertyAccess != nil {
access := commonJSPropertyAccess.AsPropertyAccessExpression()
- name := getLeftmostAccessExpression(access.Expression).AsCallExpression().Arguments.Nodes[0]
+ name := ast.GetLeftmostAccessExpression(access.Expression).AsCallExpression().Arguments.Nodes[0]
if ast.IsIdentifier(access.Name()) {
return c.resolveSymbol(c.getPropertyOfType(c.resolveExternalModuleTypeByLiteral(name), access.Name().Text()))
}
@@ -13923,7 +13927,7 @@ func (c *Checker) isOnlyImportableAsDefault(usage *ast.Node, resolvedModule *ast
}
var targetFile *ast.SourceFile
if resolvedModule != nil {
- targetFile = getSourceFileOfModule(resolvedModule)
+ targetFile = ast.GetSourceFileOfModule(resolvedModule)
}
return targetFile != nil && (ast.IsJsonSourceFile(targetFile) || tspath.GetDeclarationFileExtension(targetFile.FileName()) == ".d.json.ts")
}
@@ -14185,7 +14189,7 @@ func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnly(aliasDeclaration *ast.N
}
// If the declaration itself is type-only, mark it and return. No need to check what it resolves to.
sourceSymbol := c.getSymbolOfDeclaration(aliasDeclaration)
- if isTypeOnlyImportOrExportDeclaration(aliasDeclaration) {
+ if ast.IsTypeOnlyImportOrExportDeclaration(aliasDeclaration) {
links := c.aliasSymbolLinks.Get(sourceSymbol)
links.typeOnlyDeclaration = aliasDeclaration
return true
@@ -14206,7 +14210,7 @@ func (c *Checker) markSymbolOfAliasDeclarationIfTypeOnlyWorker(aliasDeclarationL
if target != nil && (!aliasDeclarationLinks.typeOnlyDeclarationResolved || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration == nil) {
exportSymbol := core.OrElse(target.Exports[ast.InternalSymbolNameExportEquals], target)
aliasDeclarationLinks.typeOnlyDeclarationResolved = true
- if typeOnly := core.Find(exportSymbol.Declarations, isTypeOnlyImportOrExportDeclaration); typeOnly != nil {
+ if typeOnly := core.Find(exportSymbol.Declarations, ast.IsTypeOnlyImportOrExportDeclaration); typeOnly != nil {
aliasDeclarationLinks.typeOnlyDeclaration = typeOnly
} else {
aliasDeclarationLinks.typeOnlyDeclaration = c.aliasSymbolLinks.Get(exportSymbol).typeOnlyDeclaration
@@ -16338,7 +16342,7 @@ func (c *Checker) getBindingElementTypeFromParentType(declaration *ast.Node, par
pattern := declaration.Parent
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
if c.strictNullChecks && declaration.Flags&ast.NodeFlagsAmbient != 0 && ast.IsPartOfParameterDeclaration(declaration) {
- parentType = c.getNonNullableType(parentType)
+ parentType = c.GetNonNullableType(parentType)
} else if c.strictNullChecks && pattern.Parent.Initializer() != nil && !(c.hasTypeFacts(c.getTypeOfInitializer(pattern.Parent.Initializer()), TypeFactsEQUndefined)) {
parentType = c.getTypeWithFacts(parentType, TypeFactsNEUndefined)
}
@@ -17090,20 +17094,20 @@ func (c *Checker) getNullableType(t *Type, flags TypeFlags) *Type {
return c.getUnionType([]*Type{t, c.undefinedType, c.nullType})
}
-func (c *Checker) getNonNullableType(t *Type) *Type {
+func (c *Checker) GetNonNullableType(t *Type) *Type {
if c.strictNullChecks {
return c.getAdjustedTypeWithFacts(t, TypeFactsNEUndefinedOrNull)
}
return t
}
-func (c *Checker) isNullableType(t *Type) bool {
+func (c *Checker) IsNullableType(t *Type) bool {
return c.hasTypeFacts(t, TypeFactsIsUndefinedOrNull)
}
func (c *Checker) getNonNullableTypeIfNeeded(t *Type) *Type {
- if c.isNullableType(t) {
- return c.getNonNullableType(t)
+ if c.IsNullableType(t) {
+ return c.GetNonNullableType(t)
}
return t
}
@@ -17682,7 +17686,7 @@ func (c *Checker) resolveBaseTypesOfClass(t *Type) {
}
func getBaseTypeNodeOfClass(t *Type) *ast.Node {
- decl := getClassLikeDeclarationOfSymbol(t.symbol)
+ decl := ast.GetClassLikeDeclarationOfSymbol(t.symbol)
if decl != nil {
return ast.GetExtendsHeritageClauseElement(decl)
}
@@ -18154,7 +18158,7 @@ func (c *Checker) getObjectLiteralIndexInfo(isReadonly bool, properties []*ast.S
}
func (c *Checker) isSymbolWithSymbolName(symbol *ast.Symbol) bool {
- if isKnownSymbol(symbol) {
+ if IsKnownSymbol(symbol) {
return true
}
if len(symbol.Declarations) != 0 {
@@ -18775,7 +18779,7 @@ func (c *Checker) getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(t *Ty
case contextualSignatureReturnType == nil:
// No contextual type
case isAsync:
- contextualType = c.getPromisedTypeOfPromise(contextualSignatureReturnType)
+ contextualType = c.GetPromisedTypeOfPromise(contextualSignatureReturnType)
default:
contextualType = contextualSignatureReturnType
}
@@ -19224,7 +19228,7 @@ func isThislessTypeParameter(node *ast.Node) bool {
func (c *Checker) getDefaultConstructSignatures(classType *Type) []*Signature {
baseConstructorType := c.getBaseConstructorTypeOfClass(classType)
baseSignatures := c.getSignaturesOfType(baseConstructorType, SignatureKindConstruct)
- declaration := getClassLikeDeclarationOfSymbol(classType.symbol)
+ declaration := ast.GetClassLikeDeclarationOfSymbol(classType.symbol)
isAbstract := declaration != nil && ast.HasSyntacticModifier(declaration, ast.ModifierFlagsAbstract)
if len(baseSignatures) == 0 {
flags := core.IfElse(isAbstract, SignatureFlagsConstruct|SignatureFlagsAbstract, SignatureFlagsConstruct)
@@ -20448,13 +20452,6 @@ func (c *Checker) isNamedMember(symbol *ast.Symbol, id string) bool {
return !isReservedMemberName(id) && c.symbolIsValue(symbol)
}
-// A reserved member name consists of the byte 0xFE (which is an invalid UTF-8 encoding) followed by one or more
-// characters where the first character is not '@' or '#'. The '@' character indicates that the name is denoted by
-// a well known ES Symbol instance and the '#' character indicates that the name is a PrivateIdentifier.
-func isReservedMemberName(name string) bool {
- return len(name) >= 2 && name[0] == '\xFE' && name[1] != '@' && name[1] != '#'
-}
-
func (c *Checker) symbolIsValue(symbol *ast.Symbol) bool {
return c.symbolIsValueEx(symbol, false /*includeTypeOnlyMembers*/)
}
@@ -24436,7 +24433,7 @@ func (c *Checker) addTypeToIntersection(typeSet *orderedSet[*Type], includes Typ
if flags&TypeFlagsIntersection != 0 {
return c.addTypesToIntersection(typeSet, includes, t.Types())
}
- if c.isEmptyAnonymousObjectType(t) {
+ if c.IsEmptyAnonymousObjectType(t) {
if includes&TypeFlagsIncludesEmptyObject == 0 {
includes |= TypeFlagsIncludesEmptyObject
typeSet.add(t)
@@ -24478,7 +24475,7 @@ func (c *Checker) removeRedundantSupertypes(types []*Type, includes TypeFlags) [
t.flags&TypeFlagsBigInt != 0 && includes&TypeFlagsBigIntLiteral != 0 ||
t.flags&TypeFlagsESSymbol != 0 && includes&TypeFlagsUniqueESSymbol != 0 ||
t.flags&TypeFlagsVoid != 0 && includes&TypeFlagsUndefined != 0 ||
- c.isEmptyAnonymousObjectType(t) && includes&TypeFlagsDefinitelyNonNullable != 0
+ c.IsEmptyAnonymousObjectType(t) && includes&TypeFlagsDefinitelyNonNullable != 0
if remove {
types = slices.Delete(types, i, i+1)
}
@@ -24644,7 +24641,7 @@ func (c *Checker) filterTypes(types []*Type, predicate func(*Type) bool) {
}
}
-func (c *Checker) isEmptyAnonymousObjectType(t *Type) bool {
+func (c *Checker) IsEmptyAnonymousObjectType(t *Type) bool {
return t.objectFlags&ObjectFlagsAnonymous != 0 && (t.objectFlags&ObjectFlagsMembersResolved != 0 && c.isEmptyResolvedType(t.AsStructuredType()) ||
t.symbol != nil && t.symbol.Flags&ast.SymbolFlagsTypeLiteral != 0 && len(c.getMembersOfSymbol(t.symbol)) == 0)
}
@@ -24889,7 +24886,7 @@ func (c *Checker) getLiteralTypeFromProperty(prop *ast.Symbol, include TypeFlags
if name != nil {
t = c.getLiteralTypeFromPropertyName(name)
}
- if t == nil && !isKnownSymbol(prop) {
+ if t == nil && !IsKnownSymbol(prop) {
t = c.getStringLiteralType(ast.SymbolName(prop))
}
}
@@ -24966,7 +24963,7 @@ func (c *Checker) shouldDeferIndexType(t *Type, indexFlags IndexFlags) bool {
c.isGenericTupleType(t) ||
c.isGenericMappedType(t) && (!c.hasDistributiveNameType(t) || c.getMappedTypeNameTypeKind(t) == MappedTypeNameTypeKindRemapping) ||
t.flags&TypeFlagsUnion != 0 && indexFlags&IndexFlagsNoReducibleCheck == 0 && c.isGenericReducibleType(t) ||
- t.flags&TypeFlagsIntersection != 0 && c.maybeTypeOfKind(t, TypeFlagsInstantiable) && core.Some(t.Types(), c.isEmptyAnonymousObjectType)
+ t.flags&TypeFlagsIntersection != 0 && c.maybeTypeOfKind(t, TypeFlagsInstantiable) && core.Some(t.Types(), c.IsEmptyAnonymousObjectType)
}
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N
]: X }, to simply N. This however presumes
@@ -25541,7 +25538,7 @@ func (c *Checker) isNoInferTargetType(t *Type) bool {
// examine all object type members.
return t.flags&TypeFlagsUnionOrIntersection != 0 && core.Some(t.AsUnionOrIntersectionType().types, c.isNoInferTargetType) ||
t.flags&TypeFlagsSubstitution != 0 && !c.isNoInferType(t) && c.isNoInferTargetType(t.AsSubstitutionType().baseType) ||
- t.flags&TypeFlagsObject != 0 && !c.isEmptyAnonymousObjectType(t) ||
+ t.flags&TypeFlagsObject != 0 && !c.IsEmptyAnonymousObjectType(t) ||
t.flags&(TypeFlagsInstantiable & ^TypeFlagsSubstitution) != 0 && !c.isPatternLiteralType(t)
}
@@ -25945,7 +25942,7 @@ func (c *Checker) isUnknownLikeUnionType(t *Type) bool {
if t.objectFlags&ObjectFlagsIsUnknownLikeUnionComputed == 0 {
t.objectFlags |= ObjectFlagsIsUnknownLikeUnionComputed
types := t.Types()
- if len(types) >= 3 && types[0].flags&TypeFlagsUndefined != 0 && types[1].flags&TypeFlagsNull != 0 && core.Some(types, c.isEmptyAnonymousObjectType) {
+ if len(types) >= 3 && types[0].flags&TypeFlagsUndefined != 0 && types[1].flags&TypeFlagsNull != 0 && core.Some(types, c.IsEmptyAnonymousObjectType) {
t.objectFlags |= ObjectFlagsIsUnknownLikeUnion
}
}
@@ -26160,7 +26157,7 @@ func (c *Checker) shouldNormalizeIntersection(t *Type) bool {
hasNullableOrEmpty := false
for _, t := range t.Types() {
hasInstantiable = hasInstantiable || t.flags&TypeFlagsInstantiable != 0
- hasNullableOrEmpty = hasNullableOrEmpty || t.flags&TypeFlagsNullable != 0 || c.isEmptyAnonymousObjectType(t)
+ hasNullableOrEmpty = hasNullableOrEmpty || t.flags&TypeFlagsNullable != 0 || c.IsEmptyAnonymousObjectType(t)
if hasInstantiable && hasNullableOrEmpty {
return true
}
@@ -26419,7 +26416,7 @@ func isInternalModuleImportEqualsDeclaration(node *ast.Node) bool {
func (c *Checker) markIdentifierAliasReferenced(location *ast.IdentifierNode) {
symbol := c.getResolvedSymbol(location)
- if symbol != nil && symbol != c.argumentsSymbol && symbol != c.unknownSymbol && !isThisInTypeQuery(location) {
+ if symbol != nil && symbol != c.argumentsSymbol && symbol != c.unknownSymbol && !ast.IsThisInTypeQuery(location) {
c.markAliasReferenced(symbol, location)
}
}
@@ -26578,7 +26575,7 @@ func (c *Checker) markAliasReferenced(symbol *ast.Symbol, location *ast.Node) {
if !c.canCollectSymbolAliasAccessibilityData {
return
}
- if ast.IsNonLocalAlias(symbol, ast.SymbolFlagsValue /*excludes*/) && !isInTypeQuery(location) {
+ if ast.IsNonLocalAlias(symbol, ast.SymbolFlagsValue /*excludes*/) && !IsInTypeQuery(location) {
target := c.resolveAlias(symbol)
if c.getSymbolFlagsEx(symbol, true /*excludeTypeOnlyMeanings*/, false /*excludeLocalMeanings*/)&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue) != 0 {
// An alias resolving to a const enum cannot be elided if (1) 'isolatedModules' is enabled
@@ -26648,7 +26645,7 @@ func (c *Checker) markEntityNameOrEntityExpressionAsReference(typeName *ast.Node
c.compilerOptions.GetIsolatedModules() &&
c.compilerOptions.GetEmitModuleKind() >= core.ModuleKindES2015 &&
!c.symbolIsValue(rootSymbol) &&
- !core.Some(rootSymbol.Declarations, isTypeOnlyImportOrExportDeclaration) {
+ !core.Some(rootSymbol.Declarations, ast.IsTypeOnlyImportOrExportDeclaration) {
diag := c.error(typeName, diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled)
var aliasDeclaration *ast.Node
for _, decl := range rootSymbol.Declarations {
@@ -26691,7 +26688,7 @@ func (c *Checker) markTypeNodeAsReferenced(node *ast.TypeNode) {
}
}
-func (c *Checker) getPromisedTypeOfPromise(t *Type) *Type {
+func (c *Checker) GetPromisedTypeOfPromise(t *Type) *Type {
return c.getPromisedTypeOfPromiseEx(t, nil, nil)
}
@@ -26839,7 +26836,7 @@ func isPartialMappedType(t *Type) bool {
func (c *Checker) getOptionalExpressionType(exprType *Type, expression *ast.Node) *Type {
switch {
case ast.IsExpressionOfOptionalChainRoot(expression):
- return c.getNonNullableType(exprType)
+ return c.GetNonNullableType(exprType)
case ast.IsOptionalChain(expression):
return c.removeOptionalTypeMarker(exprType)
default:
@@ -29351,7 +29348,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy
switch node.Kind {
case ast.KindIdentifier, ast.KindPrivateIdentifier, ast.KindPropertyAccessExpression, ast.KindQualifiedName:
- if !isThisInTypeQuery(node) {
+ if !ast.IsThisInTypeQuery(node) {
return c.getSymbolOfNameOrPropertyAccessExpression(node)
}
fallthrough
@@ -29799,3 +29796,13 @@ func (c *Checker) GetEmitResolver(file *ast.SourceFile, skipDiagnostics bool) pr
}
return c.emitResolver
}
+
+// !!!
+func (c *Checker) GetAccessibleSymbolChain(
+ symbol *ast.Symbol,
+ enclosingDeclaration *ast.Node,
+ meaning ast.SymbolFlags,
+ useOnlyExternalAliasing bool,
+) []*ast.Symbol {
+ return nil
+}
diff --git a/internal/checker/flow.go b/internal/checker/flow.go
index aba89e9764..58dcf30b9c 100644
--- a/internal/checker/flow.go
+++ b/internal/checker/flow.go
@@ -309,7 +309,7 @@ func (c *Checker) narrowTypeByTypePredicate(f *FlowState, t *Type, predicate *Ty
if c.isMatchingReference(f.reference, predicateArgument) {
return c.getNarrowedType(t, predicate.t, assumeTrue, false /*checkDerived*/)
}
- if c.strictNullChecks && c.optionalChainContainsReference(predicateArgument, f.reference) && (assumeTrue && !(c.hasTypeFacts(predicate.t, TypeFactsEQUndefined)) || !assumeTrue && everyType(predicate.t, c.isNullableType)) {
+ if c.strictNullChecks && c.optionalChainContainsReference(predicateArgument, f.reference) && (assumeTrue && !(c.hasTypeFacts(predicate.t, TypeFactsEQUndefined)) || !assumeTrue && everyType(predicate.t, c.IsNullableType)) {
t = c.getAdjustedTypeWithFacts(t, TypeFactsNEUndefinedOrNull)
}
access := c.getDiscriminantPropertyAccess(f, predicateArgument, t)
@@ -566,8 +566,8 @@ func (c *Checker) narrowTypeByEquality(t *Type, operator ast.Kind, value *ast.No
return c.getAdjustedTypeWithFacts(t, facts)
}
if assumeTrue {
- if !doubleEquals && (t.flags&TypeFlagsUnknown != 0 || someType(t, c.isEmptyAnonymousObjectType)) {
- if valueType.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.isEmptyAnonymousObjectType(valueType) {
+ if !doubleEquals && (t.flags&TypeFlagsUnknown != 0 || someType(t, c.IsEmptyAnonymousObjectType)) {
+ if valueType.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 || c.IsEmptyAnonymousObjectType(valueType) {
return valueType
}
if valueType.flags&TypeFlagsObject != 0 {
@@ -812,7 +812,7 @@ func (c *Checker) narrowTypeByInstanceof(f *FlowState, t *Type, expr *ast.Binary
instanceType := c.mapType(rightType, c.getInstanceType)
// Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow
// in the false branch only if the target is a non-empty object type.
- if IsTypeAny(t) && (instanceType == c.globalObjectType || instanceType == c.globalFunctionType) || !assumeTrue && !(instanceType.flags&TypeFlagsObject != 0 && !c.isEmptyAnonymousObjectType(instanceType)) {
+ if IsTypeAny(t) && (instanceType == c.globalObjectType || instanceType == c.globalFunctionType) || !assumeTrue && !(instanceType.flags&TypeFlagsObject != 0 && !c.IsEmptyAnonymousObjectType(instanceType)) {
return t
}
return c.getNarrowedType(t, instanceType, assumeTrue, true /*checkDerived*/)
@@ -1563,7 +1563,7 @@ func (c *Checker) isMatchingReference(source *ast.Node, target *ast.Node) bool {
case ast.KindMetaProperty:
return ast.IsMetaProperty(target) && source.AsMetaProperty().KeywordToken == target.AsMetaProperty().KeywordToken && source.Name().Text() == target.Name().Text()
case ast.KindIdentifier, ast.KindPrivateIdentifier:
- if isThisInTypeQuery(source) {
+ if ast.IsThisInTypeQuery(source) {
return target.Kind == ast.KindThisKeyword
}
return ast.IsIdentifier(target) && c.getResolvedSymbol(source) == c.getResolvedSymbol(target) ||
@@ -1619,7 +1619,7 @@ func (c *Checker) getFlowReferenceKey(f *FlowState) string {
func (c *Checker) writeFlowCacheKey(b *KeyBuilder, node *ast.Node, declaredType *Type, initialType *Type, flowContainer *ast.Node) bool {
switch node.Kind {
case ast.KindIdentifier:
- if !isThisInTypeQuery(node) {
+ if !ast.IsThisInTypeQuery(node) {
symbol := c.getResolvedSymbol(node)
if symbol == c.unknownSymbol {
return false
@@ -1778,7 +1778,7 @@ func (c *Checker) isConstantReference(node *ast.Node) bool {
case ast.KindThisKeyword:
return true
case ast.KindIdentifier:
- if !isThisInTypeQuery(node) {
+ if !ast.IsThisInTypeQuery(node) {
symbol := c.getResolvedSymbol(node)
return c.isConstantVariable(symbol) || c.isParameterOrMutableLocalVariable(symbol) && !c.isSymbolAssigned(symbol) || symbol.ValueDeclaration != nil && ast.IsFunctionExpression(symbol.ValueDeclaration)
}
@@ -2423,7 +2423,7 @@ func (c *Checker) getFlowTypeInConstructor(symbol *ast.Symbol, constructor *ast.
c.error(symbol.ValueDeclaration, diagnostics.Member_0_implicitly_has_an_1_type, c.symbolToString(symbol), c.TypeToString(flowType))
}
// We don't infer a type if assignments are only null or undefined.
- if everyType(flowType, c.isNullableType) {
+ if everyType(flowType, c.IsNullableType) {
return nil
}
return c.convertAutoToAny(flowType)
@@ -2446,7 +2446,7 @@ func (c *Checker) getFlowTypeInStaticBlocks(symbol *ast.Symbol, staticBlocks []*
c.error(symbol.ValueDeclaration, diagnostics.Member_0_implicitly_has_an_1_type, c.symbolToString(symbol), c.TypeToString(flowType))
}
// We don't infer a type if assignments are only null or undefined.
- if everyType(flowType, c.isNullableType) {
+ if everyType(flowType, c.IsNullableType) {
continue
}
return c.convertAutoToAny(flowType)
diff --git a/internal/checker/inference.go b/internal/checker/inference.go
index 7fb543263a..80e465aada 100644
--- a/internal/checker/inference.go
+++ b/internal/checker/inference.go
@@ -759,7 +759,7 @@ func (c *Checker) inferFromProperties(n *InferenceState, source *Type, target *T
properties := c.getPropertiesOfObjectType(target)
for _, targetProp := range properties {
sourceProp := c.getPropertyOfType(source, targetProp.Name)
- if sourceProp != nil && !core.Some(sourceProp.Declarations, c.hasSkipDirectInferenceFlag) {
+ if sourceProp != nil && !core.Some(sourceProp.Declarations, c.isSkipDirectInferenceNode) {
c.inferFromTypes(n, c.removeMissingType(c.getTypeOfSymbol(sourceProp), sourceProp.Flags&ast.SymbolFlagsOptional != 0), c.removeMissingType(c.getTypeOfSymbol(targetProp), targetProp.Flags&ast.SymbolFlagsOptional != 0))
}
}
@@ -1514,13 +1514,11 @@ func (c *Checker) literalTypesWithSameBaseType(types []*Type) bool {
}
func (c *Checker) isFromInferenceBlockedSource(t *Type) bool {
- return t.symbol != nil && core.Some(t.symbol.Declarations, c.hasSkipDirectInferenceFlag)
+ return t.symbol != nil && core.Some(t.symbol.Declarations, c.isSkipDirectInferenceNode)
}
-func (c *Checker) hasSkipDirectInferenceFlag(node *ast.Node) bool {
- // !!! Make this a new NodeFlag
- // return c.getNodeLinks(node).skipDirectInference
- return false
+func (c *Checker) isSkipDirectInferenceNode(node *ast.Node) bool {
+ return c.skipDirectInferenceNodes.Has(node)
}
// Returns `true` if `type` has the shape `[T[0]]` where `T` is `typeParameter`
diff --git a/internal/checker/relater.go b/internal/checker/relater.go
index 0874e542a5..907307ed7b 100644
--- a/internal/checker/relater.go
+++ b/internal/checker/relater.go
@@ -256,7 +256,7 @@ func (c *Checker) isSimpleTypeRelatedTo(source *Type, target *Type, relation *Re
if s&TypeFlagsNull != 0 && (!c.strictNullChecks && t&TypeFlagsUnionOrIntersection == 0 || t&TypeFlagsNull != 0) {
return true
}
- if s&TypeFlagsObject != 0 && t&TypeFlagsNonPrimitive != 0 && !(relation == c.strictSubtypeRelation && c.isEmptyAnonymousObjectType(source) && source.objectFlags&ObjectFlagsFreshLiteral == 0) {
+ if s&TypeFlagsObject != 0 && t&TypeFlagsNonPrimitive != 0 && !(relation == c.strictSubtypeRelation && c.IsEmptyAnonymousObjectType(source) && source.objectFlags&ObjectFlagsFreshLiteral == 0) {
return true
}
if relation == c.assignableRelation || relation == c.comparableRelation {
@@ -1533,10 +1533,10 @@ func (c *Checker) compareSignaturesRelated(source *Signature, target *Signature,
var sourceSig *Signature
var targetSig *Signature
if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(source, i) {
- sourceSig = c.getSingleCallSignature(c.getNonNullableType(sourceType))
+ sourceSig = c.getSingleCallSignature(c.GetNonNullableType(sourceType))
}
if checkMode&SignatureCheckModeCallback == 0 && !c.isInstantiatedGenericParameter(target, i) {
- targetSig = c.getSingleCallSignature(c.getNonNullableType(targetType))
+ targetSig = c.getSingleCallSignature(c.GetNonNullableType(targetType))
}
callbacks := sourceSig != nil && targetSig != nil && c.getTypePredicateOfSignature(sourceSig) == nil && c.getTypePredicateOfSignature(targetSig) == nil &&
c.getTypeFacts(sourceType, TypeFactsIsUndefinedOrNull) == c.getTypeFacts(targetType, TypeFactsIsUndefinedOrNull)
@@ -2266,7 +2266,7 @@ func (c *Checker) getEffectiveConstraintOfIntersection(types []*Type, targetIsUn
constraints = append(constraints, t)
}
}
- } else if t.flags&TypeFlagsDisjointDomains != 0 || c.isEmptyAnonymousObjectType(t) {
+ } else if t.flags&TypeFlagsDisjointDomains != 0 || c.IsEmptyAnonymousObjectType(t) {
hasDisjointDomainType = true
}
}
@@ -2277,7 +2277,7 @@ func (c *Checker) getEffectiveConstraintOfIntersection(types []*Type, targetIsUn
// We add any types belong to one of the disjoint domains because they might cause the final
// intersection operation to reduce the union constraints.
for _, t := range types {
- if t.flags&TypeFlagsDisjointDomains != 0 || c.isEmptyAnonymousObjectType(t) {
+ if t.flags&TypeFlagsDisjointDomains != 0 || c.IsEmptyAnonymousObjectType(t) {
constraints = append(constraints, t)
}
}
@@ -4885,10 +4885,10 @@ func (c *Checker) isTypeDerivedFrom(source *Type, target *Type) bool {
constraint = c.unknownType
}
return c.isTypeDerivedFrom(constraint, target)
- case c.isEmptyAnonymousObjectType(target):
+ case c.IsEmptyAnonymousObjectType(target):
return source.flags&(TypeFlagsObject|TypeFlagsNonPrimitive) != 0
case target == c.globalObjectType:
- return source.flags&(TypeFlagsObject|TypeFlagsNonPrimitive) != 0 && !c.isEmptyAnonymousObjectType(source)
+ return source.flags&(TypeFlagsObject|TypeFlagsNonPrimitive) != 0 && !c.IsEmptyAnonymousObjectType(source)
case target == c.globalFunctionType:
return source.flags&TypeFlagsObject != 0 && c.isFunctionObjectType(source)
default:
diff --git a/internal/checker/services.go b/internal/checker/services.go
new file mode 100644
index 0000000000..9b5863d2ae
--- /dev/null
+++ b/internal/checker/services.go
@@ -0,0 +1,400 @@
+package checker
+
+import (
+ "maps"
+ "slices"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/core"
+)
+
+func (c *Checker) GetSymbolsInScope(location *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol {
+ return c.getSymbolsInScope(location, meaning)
+}
+
+func (c *Checker) getSymbolsInScope(location *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol {
+ if location.Flags&ast.NodeFlagsInWithStatement != 0 {
+ // We cannot answer semantic questions within a with block, do not proceed any further
+ return nil
+ }
+
+ symbols := createSymbolTable(nil)
+ isStaticSymbol := false
+
+ // Copy the given symbol into symbol tables if the symbol has the given meaning
+ // and it doesn't already exists in the symbol table.
+ copySymbol := func(symbol *ast.Symbol, meaning ast.SymbolFlags) {
+ if GetCombinedLocalAndExportSymbolFlags(symbol)&meaning != 0 {
+ id := symbol.Name
+ // We will copy all symbol regardless of its reserved name because
+ // symbolsToArray will check whether the key is a reserved name and
+ // it will not copy symbol with reserved name to the array
+ if _, ok := symbols[id]; !ok {
+ symbols[id] = symbol
+ }
+ }
+ }
+
+ copySymbols := func(source ast.SymbolTable, meaning ast.SymbolFlags) {
+ if meaning != 0 {
+ for _, symbol := range source {
+ copySymbol(symbol, meaning)
+ }
+ }
+ }
+
+ copyLocallyVisibleExportSymbols := func(source ast.SymbolTable, meaning ast.SymbolFlags) {
+ if meaning != 0 {
+ for _, symbol := range source {
+ // Similar condition as in `resolveNameHelper`
+ if ast.GetDeclarationOfKind(symbol, ast.KindExportSpecifier) == nil &&
+ ast.GetDeclarationOfKind(symbol, ast.KindNamespaceExport) == nil &&
+ symbol.Name != ast.InternalSymbolNameDefault {
+ copySymbol(symbol, meaning)
+ }
+ }
+ }
+ }
+
+ populateSymbols := func() {
+ for location != nil {
+ if canHaveLocals(location) && location.Locals() != nil && !ast.IsGlobalSourceFile(location) {
+ copySymbols(location.Locals(), meaning)
+ }
+
+ switch location.Kind {
+ case ast.KindSourceFile:
+ if !ast.IsExternalModule(location.AsSourceFile()) {
+ break
+ }
+ fallthrough
+ case ast.KindModuleDeclaration:
+ copyLocallyVisibleExportSymbols(c.getSymbolOfDeclaration(location).Exports, meaning&ast.SymbolFlagsModuleMember)
+ case ast.KindEnumDeclaration:
+ copySymbols(c.getSymbolOfDeclaration(location).Exports, meaning&ast.SymbolFlagsEnumMember)
+ case ast.KindClassExpression:
+ className := location.AsClassExpression().Name()
+ if className != nil {
+ copySymbol(location.Symbol(), meaning)
+ }
+ // this fall-through is necessary because we would like to handle
+ // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration.
+ fallthrough
+ case ast.KindClassDeclaration, ast.KindInterfaceDeclaration:
+ // If we didn't come from static member of class or interface,
+ // add the type parameters into the symbol table
+ // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol.
+ // Note: that the memberFlags come from previous iteration.
+ if !isStaticSymbol {
+ copySymbols(c.getMembersOfSymbol(c.getSymbolOfDeclaration(location)), meaning&ast.SymbolFlagsType)
+ }
+ case ast.KindFunctionExpression:
+ funcName := location.Name()
+ if funcName != nil {
+ copySymbol(location.Symbol(), meaning)
+ }
+ }
+
+ if introducesArgumentsExoticObject(location) {
+ copySymbol(c.argumentsSymbol, meaning)
+ }
+
+ isStaticSymbol = ast.IsStatic(location)
+ location = location.Parent
+ }
+
+ copySymbols(c.globals, meaning)
+ }
+
+ populateSymbols()
+
+ delete(symbols, ast.InternalSymbolNameThis) // Not a symbol, a keyword
+ return symbolsToArray(symbols)
+}
+
+func (c *Checker) GetAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
+ return c.resolveAlias(symbol)
+}
+
+func (c *Checker) GetExportsOfModule(symbol *ast.Symbol) []*ast.Symbol {
+ return symbolsToArray(c.getExportsOfModule(symbol))
+}
+
+func (c *Checker) IsValidPropertyAccess(node *ast.Node, propertyName string) bool {
+ return c.isValidPropertyAccess(node, propertyName)
+}
+
+func (c *Checker) isValidPropertyAccess(node *ast.Node, propertyName string) bool {
+ switch node.Kind {
+ case ast.KindPropertyAccessExpression:
+ return c.isValidPropertyAccessWithType(node, node.Expression().Kind == ast.KindSuperKeyword, propertyName, c.getWidenedType(c.checkExpression(node.Expression())))
+ case ast.KindQualifiedName:
+ return c.isValidPropertyAccessWithType(node, false /*isSuper*/, propertyName, c.getWidenedType(c.checkExpression(node.AsQualifiedName().Left)))
+ case ast.KindImportType:
+ return c.isValidPropertyAccessWithType(node, false /*isSuper*/, propertyName, c.getTypeFromTypeNode(node))
+ }
+ panic("Unexpected node kind in isValidPropertyAccess: " + node.Kind.String())
+}
+
+func (c *Checker) isValidPropertyAccessWithType(node *ast.Node, isSuper bool, propertyName string, t *Type) bool {
+ // Short-circuiting for improved performance.
+ if IsTypeAny(t) {
+ return true
+ }
+
+ prop := c.getPropertyOfType(t, propertyName)
+ return prop != nil && c.isPropertyAccessible(node, isSuper, false /*isWrite*/, t, prop)
+}
+
+// Checks if an existing property access is valid for completions purposes.
+// node: a property access-like node where we want to check if we can access a property.
+// This node does not need to be an access of the property we are checking.
+// e.g. in completions, this node will often be an incomplete property access node, as in `foo.`.
+// Besides providing a location (i.e. scope) used to check property accessibility, we use this node for
+// computing whether this is a `super` property access.
+// type: the type whose property we are checking.
+// property: the accessed property's symbol.
+func (c *Checker) IsValidPropertyAccessForCompletions(node *ast.Node, t *Type, property *ast.Symbol) bool {
+ return c.isPropertyAccessible(
+ node,
+ node.Kind == ast.KindPropertyAccessExpression && node.Expression().Kind == ast.KindSuperKeyword,
+ false, /*isWrite*/
+ t,
+ property,
+ )
+ // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context.
+}
+
+func (c *Checker) GetAllPossiblePropertiesOfTypes(types []*Type) []*ast.Symbol {
+ unionType := c.getUnionType(types)
+ if unionType.flags&TypeFlagsUnion == 0 {
+ return c.getAugmentedPropertiesOfType(unionType)
+ }
+
+ props := createSymbolTable(nil)
+ for _, memberType := range types {
+ augmentedProps := c.getAugmentedPropertiesOfType(memberType)
+ for _, p := range augmentedProps {
+ if _, ok := props[p.Name]; !ok {
+ prop := c.createUnionOrIntersectionProperty(unionType, p.Name, false /*skipObjectFunctionPropertyAugment*/)
+ // May be undefined if the property is private
+ if prop != nil {
+ props[p.Name] = prop
+ }
+ }
+ }
+ }
+ return slices.Collect(maps.Values(props))
+}
+
+func (c *Checker) IsUnknownSymbol(symbol *ast.Symbol) bool {
+ return symbol == c.unknownSymbol
+}
+
+func (c *Checker) IsUndefinedSymbol(symbol *ast.Symbol) bool {
+ return symbol == c.undefinedSymbol
+}
+
+func (c *Checker) IsArgumentsSymbol(symbol *ast.Symbol) bool {
+ return symbol == c.argumentsSymbol
+}
+
+// Originally from services.ts
+func (c *Checker) GetNonOptionalType(t *Type) *Type {
+ return c.removeOptionalTypeMarker(t)
+}
+
+func (c *Checker) GetStringIndexType(t *Type) *Type {
+ return c.getIndexTypeOfType(t, c.stringType)
+}
+
+func (c *Checker) GetNumberIndexType(t *Type) *Type {
+ return c.getIndexTypeOfType(t, c.numberType)
+}
+
+func (c *Checker) GetCallSignatures(t *Type) []*Signature {
+ return c.getSignaturesOfType(t, SignatureKindCall)
+}
+
+func (c *Checker) GetConstructSignatures(t *Type) []*Signature {
+ return c.getSignaturesOfType(t, SignatureKindConstruct)
+}
+
+func (c *Checker) GetApparentProperties(t *Type) []*ast.Symbol {
+ return c.getAugmentedPropertiesOfType(t)
+}
+
+func (c *Checker) getAugmentedPropertiesOfType(t *Type) []*ast.Symbol {
+ t = c.getApparentType(t)
+ propsByName := createSymbolTable(c.getPropertiesOfType(t))
+ var functionType *Type
+ if len(c.getSignaturesOfType(t, SignatureKindCall)) > 0 {
+ functionType = c.globalCallableFunctionType
+ } else if len(c.getSignaturesOfType(t, SignatureKindConstruct)) > 0 {
+ functionType = c.globalNewableFunctionType
+ }
+
+ if functionType != nil {
+ for _, p := range c.getPropertiesOfType(functionType) {
+ if _, ok := propsByName[p.Name]; !ok {
+ propsByName[p.Name] = p
+ }
+ }
+ }
+ return c.getNamedMembers(propsByName)
+}
+
+func (c *Checker) TryGetMemberInModuleExportsAndProperties(memberName string, moduleSymbol *ast.Symbol) *ast.Symbol {
+ symbol := c.TryGetMemberInModuleExports(memberName, moduleSymbol)
+ if symbol != nil {
+ return symbol
+ }
+
+ exportEquals := c.resolveExternalModuleSymbol(moduleSymbol, false /*dontResolveAlias*/)
+ if exportEquals == moduleSymbol {
+ return nil
+ }
+
+ t := c.getTypeOfSymbol(exportEquals)
+ if c.shouldTreatPropertiesOfExternalModuleAsExports(t) {
+ return c.getPropertyOfType(t, memberName)
+ }
+ return nil
+}
+
+func (c *Checker) TryGetMemberInModuleExports(memberName string, moduleSymbol *ast.Symbol) *ast.Symbol {
+ symbolTable := c.getExportsOfModule(moduleSymbol)
+ return symbolTable[memberName]
+}
+
+func (c *Checker) shouldTreatPropertiesOfExternalModuleAsExports(resolvedExternalModuleType *Type) bool {
+ return resolvedExternalModuleType.flags&TypeFlagsPrimitive == 0 ||
+ resolvedExternalModuleType.objectFlags&ObjectFlagsClass != 0 ||
+ // `isArrayOrTupleLikeType` is too expensive to use in this auto-imports hot path.
+ c.isArrayType(resolvedExternalModuleType) ||
+ isTupleType(resolvedExternalModuleType)
+}
+
+func (c *Checker) GetContextualType(node *ast.Expression, contextFlags ContextFlags) *Type {
+ if contextFlags&ContextFlagsCompletions != 0 {
+ return runWithInferenceBlockedFromSourceNode(c, node, func() *Type { return c.getContextualType(node, contextFlags) })
+ }
+ return c.getContextualType(node, contextFlags)
+}
+
+func runWithInferenceBlockedFromSourceNode[T any](c *Checker, node *ast.Node, fn func() T) T {
+ containingCall := ast.FindAncestor(node, ast.IsCallLikeExpression)
+ if containingCall != nil {
+ toMarkSkip := node
+ for {
+ c.skipDirectInferenceNodes.Add(toMarkSkip)
+ toMarkSkip = toMarkSkip.Parent
+ if toMarkSkip == nil || toMarkSkip == containingCall {
+ break
+ }
+ }
+ }
+
+ c.isInferencePartiallyBlocked = true
+ result := runWithoutResolvedSignatureCaching(c, node, fn)
+ c.isInferencePartiallyBlocked = false
+
+ c.skipDirectInferenceNodes.Clear()
+ return result
+}
+
+func runWithoutResolvedSignatureCaching[T any](c *Checker, node *ast.Node, fn func() T) T {
+ ancestorNode := ast.FindAncestor(node, func(n *ast.Node) bool {
+ return ast.IsCallLikeOrFunctionLikeExpression(n)
+ })
+ if ancestorNode != nil {
+ cachedResolvedSignatures := make(map[*SignatureLinks]*Signature)
+ cachedTypes := make(map[*ValueSymbolLinks]*Type)
+ for ancestorNode != nil {
+ signatureLinks := c.signatureLinks.Get(ancestorNode)
+ cachedResolvedSignatures[signatureLinks] = signatureLinks.resolvedSignature
+ signatureLinks.resolvedSignature = nil
+ if ast.IsFunctionExpressionOrArrowFunction(ancestorNode) {
+ symbolLinks := c.valueSymbolLinks.Get(c.getSymbolOfDeclaration(ancestorNode))
+ resolvedType := symbolLinks.resolvedType
+ cachedTypes[symbolLinks] = resolvedType
+ symbolLinks.resolvedType = nil
+ }
+ ancestorNode = ast.FindAncestor(ancestorNode.Parent, ast.IsCallLikeOrFunctionLikeExpression)
+ }
+ result := fn()
+ for signatureLinks, resolvedSignature := range cachedResolvedSignatures {
+ signatureLinks.resolvedSignature = resolvedSignature
+ }
+ for symbolLinks, resolvedType := range cachedTypes {
+ symbolLinks.resolvedType = resolvedType
+ }
+ return result
+ }
+ return fn()
+}
+
+func (c *Checker) GetRootSymbols(symbol *ast.Symbol) []*ast.Symbol {
+ roots := c.getImmediateRootSymbols(symbol)
+ if roots != nil {
+ var result []*ast.Symbol
+ for _, root := range roots {
+ result = append(result, c.GetRootSymbols(root)...)
+ }
+ }
+ return []*ast.Symbol{symbol}
+}
+
+func (c *Checker) getImmediateRootSymbols(symbol *ast.Symbol) []*ast.Symbol {
+ if symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
+ return core.MapNonNil(
+ c.valueSymbolLinks.Get(symbol).containingType.Types(),
+ func(t *Type) *ast.Symbol {
+ return c.getPropertyOfType(t, symbol.Name)
+ })
+ } else if symbol.Flags&ast.SymbolFlagsTransient != 0 {
+ if c.spreadLinks.Has(symbol) {
+ leftSpread := c.spreadLinks.Get(symbol).leftSpread
+ rightSpread := c.spreadLinks.Get(symbol).rightSpread
+ if leftSpread != nil {
+ return []*ast.Symbol{leftSpread, rightSpread}
+ }
+ }
+ if c.mappedSymbolLinks.Has(symbol) {
+ syntheticOrigin := c.mappedSymbolLinks.Get(symbol).syntheticOrigin
+ if syntheticOrigin != nil {
+ return []*ast.Symbol{syntheticOrigin}
+ }
+ }
+ target := c.tryGetTarget(symbol)
+ if target != nil {
+ return []*ast.Symbol{target}
+ }
+ return nil
+ }
+
+ return nil
+}
+
+func (c *Checker) tryGetTarget(symbol *ast.Symbol) *ast.Symbol {
+ var target *ast.Symbol
+ next := symbol
+ for {
+ if c.valueSymbolLinks.Has(next) {
+ next = c.valueSymbolLinks.Get(next).target
+ } else if c.exportTypeLinks.Has(next) {
+ next = c.exportTypeLinks.Get(next).target
+ } else {
+ next = nil
+ }
+ if next == nil {
+ break
+ }
+ target = next
+ }
+ return target
+}
+
+func (c *Checker) GetExportSymbolOfSymbol(symbol *ast.Symbol) *ast.Symbol {
+ return c.getMergedSymbol(core.IfElse(symbol.ExportSymbol != nil, symbol.ExportSymbol, symbol))
+}
diff --git a/internal/checker/types.go b/internal/checker/types.go
index a99e7f7c43..9da2867153 100644
--- a/internal/checker/types.go
+++ b/internal/checker/types.go
@@ -636,6 +636,46 @@ func (t *Type) TargetTupleType() *TupleType {
return t.AsTypeReference().target.AsTupleType()
}
+func (t *Type) Symbol() *ast.Symbol {
+ return t.symbol
+}
+
+func (t *Type) IsUnion() bool {
+ return t.flags&TypeFlagsUnion != 0
+}
+
+func (t *Type) IsString() bool {
+ return t.flags&TypeFlagsString != 0
+}
+
+func (t *Type) IsIntersection() bool {
+ return t.flags&TypeFlagsIntersection != 0
+}
+
+func (t *Type) IsStringLiteral() bool {
+ return t.flags&TypeFlagsStringLiteral != 0
+}
+
+func (t *Type) IsNumberLiteral() bool {
+ return t.flags&TypeFlagsNumberLiteral != 0
+}
+
+func (t *Type) IsBigIntLiteral() bool {
+ return t.flags&TypeFlagsBigIntLiteral != 0
+}
+
+func (t *Type) IsEnumLiteral() bool {
+ return t.flags&TypeFlagsEnumLiteral != 0
+}
+
+func (t *Type) IsBooleanLike() bool {
+ return t.flags&TypeFlagsBooleanLike != 0
+}
+
+func (t *Type) IsStringLike() bool {
+ return t.flags&TypeFlagsStringLike != 0
+}
+
// TypeData
type TypeData interface {
@@ -680,6 +720,10 @@ type LiteralType struct {
regularType *Type // Regular version of type
}
+func (t *LiteralType) Value() any {
+ return t.value
+}
+
// UniqueESSymbolTypeData
type UniqueESSymbolType struct {
diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go
index ca369921b6..7d079abc73 100644
--- a/internal/checker/utilities.go
+++ b/internal/checker/utilities.go
@@ -5,6 +5,7 @@ import (
"slices"
"strings"
"sync"
+ "unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/binder"
@@ -110,20 +111,13 @@ func isVariableDeclarationInitializedWithRequireHelper(node *ast.Node, allowAcce
if node.Kind == ast.KindVariableDeclaration && node.AsVariableDeclaration().Initializer != nil {
initializer := node.AsVariableDeclaration().Initializer
if allowAccessedRequire {
- initializer = getLeftmostAccessExpression(initializer)
+ initializer = ast.GetLeftmostAccessExpression(initializer)
}
return isRequireCall(initializer, true /*requireStringLiteralLikeArgument*/)
}
return false
}
-func getLeftmostAccessExpression(expr *ast.Node) *ast.Node {
- for ast.IsAccessExpression(expr) {
- expr = expr.Expression()
- }
- return expr
-}
-
func isRequireCall(node *ast.Node, requireStringLiteralLikeArgument bool) bool {
if ast.IsCallExpression(node) {
callExpression := node.AsCallExpression()
@@ -230,7 +224,7 @@ func isTypeReferenceIdentifier(node *ast.Node) bool {
return ast.IsTypeReferenceNode(node.Parent)
}
-func isInTypeQuery(node *ast.Node) bool {
+func IsInTypeQuery(node *ast.Node) bool {
// TypeScript 1.0 spec (April 2014): 3.6.3
// A type query consists of the keyword typeof followed by an expression.
// The expression is restricted to a single identifier or a sequence of identifiers separated by periods
@@ -245,37 +239,6 @@ func isInTypeQuery(node *ast.Node) bool {
}) != nil
}
-func isTypeOnlyImportDeclaration(node *ast.Node) bool {
- switch node.Kind {
- case ast.KindImportSpecifier:
- return node.AsImportSpecifier().IsTypeOnly || node.Parent.Parent.AsImportClause().IsTypeOnly
- case ast.KindNamespaceImport:
- return node.Parent.AsImportClause().IsTypeOnly
- case ast.KindImportClause:
- return node.AsImportClause().IsTypeOnly
- case ast.KindImportEqualsDeclaration:
- return node.AsImportEqualsDeclaration().IsTypeOnly
- }
- return false
-}
-
-func isTypeOnlyExportDeclaration(node *ast.Node) bool {
- switch node.Kind {
- case ast.KindExportSpecifier:
- return node.AsExportSpecifier().IsTypeOnly || node.Parent.Parent.AsExportDeclaration().IsTypeOnly
- case ast.KindExportDeclaration:
- d := node.AsExportDeclaration()
- return d.IsTypeOnly && d.ModuleSpecifier != nil && d.ExportClause == nil
- case ast.KindNamespaceExport:
- return node.Parent.AsExportDeclaration().IsTypeOnly
- }
- return false
-}
-
-func isTypeOnlyImportOrExportDeclaration(node *ast.Node) bool {
- return isTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node)
-}
-
func getNameFromImportDeclaration(node *ast.Node) *ast.Node {
switch node.Kind {
case ast.KindImportSpecifier:
@@ -460,7 +423,7 @@ func isSideEffectImport(node *ast.Node) bool {
func getExternalModuleRequireArgument(node *ast.Node) *ast.Node {
if isVariableDeclarationInitializedToBareOrAccessedRequire(node) {
- return getLeftmostAccessExpression(node.AsVariableDeclaration().Initializer).AsCallExpression().Arguments.Nodes[0]
+ return ast.GetLeftmostAccessExpression(node.AsVariableDeclaration().Initializer).AsCallExpression().Arguments.Nodes[0]
}
return nil
}
@@ -483,26 +446,8 @@ func isRightSideOfQualifiedNameOrPropertyAccess(node *ast.Node) bool {
return false
}
-func getSourceFileOfModule(module *ast.Symbol) *ast.SourceFile {
- declaration := module.ValueDeclaration
- if declaration == nil {
- declaration = getNonAugmentationDeclaration(module)
- }
- return ast.GetSourceFileOfNode(declaration)
-}
-
-func getNonAugmentationDeclaration(symbol *ast.Symbol) *ast.Node {
- return core.Find(symbol.Declarations, func(d *ast.Node) bool {
- return !isExternalModuleAugmentation(d) && !ast.IsGlobalScopeAugmentation(d)
- })
-}
-
-func isExternalModuleAugmentation(node *ast.Node) bool {
- return ast.IsAmbientModule(node) && ast.IsModuleAugmentationExternal(node)
-}
-
func isTopLevelInExternalModuleAugmentation(node *ast.Node) bool {
- return node != nil && node.Parent != nil && ast.IsModuleBlock(node.Parent) && isExternalModuleAugmentation(node.Parent.Parent)
+ return node != nil && node.Parent != nil && ast.IsModuleBlock(node.Parent) && ast.IsExternalModuleAugmentation(node.Parent.Parent)
}
func isSyntacticDefault(node *ast.Node) bool {
@@ -957,20 +902,6 @@ func compareTypeMappers(m1, m2 *TypeMapper) int {
return 0
}
-func getClassLikeDeclarationOfSymbol(symbol *ast.Symbol) *ast.Node {
- return core.Find(symbol.Declarations, ast.IsClassLike)
-}
-
-func isThisInTypeQuery(node *ast.Node) bool {
- if !ast.IsThisIdentifier(node) {
- return false
- }
- for ast.IsQualifiedName(node.Parent) && node.Parent.AsQualifiedName().Left == node {
- node = node.Parent
- }
- return node.Parent.Kind == ast.KindTypeQuery
-}
-
func getDeclarationModifierFlagsFromSymbol(s *ast.Symbol) ast.ModifierFlags {
return getDeclarationModifierFlagsFromSymbolEx(s, false /*isWrite*/)
}
@@ -1248,7 +1179,7 @@ func isVariableDeclarationInVariableStatement(node *ast.Node) bool {
return ast.IsVariableDeclarationList(node.Parent) && ast.IsVariableStatement(node.Parent.Parent)
}
-func isKnownSymbol(symbol *ast.Symbol) bool {
+func IsKnownSymbol(symbol *ast.Symbol) bool {
return isLateBoundName(symbol.Name)
}
@@ -1294,15 +1225,6 @@ func isThisTypeParameter(t *Type) bool {
return t.flags&TypeFlagsTypeParameter != 0 && t.AsTypeParameter().isThisType
}
-func isCallLikeExpression(node *ast.Node) bool {
- switch node.Kind {
- case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement, ast.KindCallExpression, ast.KindNewExpression,
- ast.KindTaggedTemplateExpression, ast.KindDecorator:
- return true
- }
- return false
-}
-
func isCallOrNewExpression(node *ast.Node) bool {
return ast.IsCallExpression(node) || ast.IsNewExpression(node)
}
@@ -1790,7 +1712,7 @@ func canIncludeBindAndCheckDiagnostics(sourceFile *ast.SourceFile, options *core
}
isJS := sourceFile.ScriptKind == core.ScriptKindJS || sourceFile.ScriptKind == core.ScriptKindJSX
- isCheckJS := isJS && isCheckJSEnabledForFile(sourceFile, options)
+ isCheckJS := isJS && ast.IsCheckJSEnabledForFile(sourceFile, options)
isPlainJS := isPlainJSFile(sourceFile, options.CheckJs)
// By default, only type-check .ts, .tsx, Deferred, plain JS, checked JS and External
@@ -1800,13 +1722,6 @@ func canIncludeBindAndCheckDiagnostics(sourceFile *ast.SourceFile, options *core
return isPlainJS || isCheckJS || sourceFile.ScriptKind == core.ScriptKindDeferred
}
-func isCheckJSEnabledForFile(sourceFile *ast.SourceFile, compilerOptions *core.CompilerOptions) bool {
- if sourceFile.CheckJsDirective != nil {
- return sourceFile.CheckJsDirective.Enabled
- }
- return compilerOptions.CheckJs == core.TSTrue
-}
-
func isPlainJSFile(file *ast.SourceFile, checkJs core.Tristate) bool {
return file != nil && (file.ScriptKind == core.ScriptKindJS || file.ScriptKind == core.ScriptKindJSX) && file.CheckJsDirective == nil && checkJs == core.TSUnknown
}
@@ -2157,3 +2072,50 @@ func allDeclarationsInSameSourceFile(symbol *ast.Symbol) bool {
}
return true
}
+
+// A reserved member name consists of the byte 0xFE (which is an invalid UTF-8 encoding) followed by one or more
+// characters where the first character is not '@' or '#'. The '@' character indicates that the name is denoted by
+// a well known ES Symbol instance and the '#' character indicates that the name is a PrivateIdentifier.
+func isReservedMemberName(name string) bool {
+ return len(name) >= 2 && name[0] == '\xFE' && name[1] != '@' && name[1] != '#'
+}
+
+func introducesArgumentsExoticObject(node *ast.Node) bool {
+ switch node.Kind {
+ case ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor,
+ ast.KindSetAccessor, ast.KindFunctionDeclaration, ast.KindFunctionExpression:
+ return true
+ }
+ return false
+}
+
+func symbolsToArray(symbols ast.SymbolTable) []*ast.Symbol {
+ var result []*ast.Symbol
+ for id, symbol := range symbols {
+ if !isReservedMemberName(id) {
+ result = append(result, symbol)
+ }
+ }
+ return result
+}
+
+// See comment on `declareModuleMember` in `binder.go`.
+func GetCombinedLocalAndExportSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags {
+ if symbol.ExportSymbol != nil {
+ return symbol.Flags | symbol.ExportSymbol.Flags
+ }
+ return symbol.Flags
+}
+
+func SkipAlias(symbol *ast.Symbol, checker *Checker) *ast.Symbol {
+ if symbol.Flags&ast.SymbolFlagsAlias != 0 {
+ return checker.GetAliasedSymbol(symbol)
+ }
+ return symbol
+}
+
+// True if the symbol is for an external module, as opposed to a namespace.
+func IsExternalModuleSymbol(moduleSymbol *ast.Symbol) bool {
+ firstRune, _ := utf8.DecodeRuneInString(moduleSymbol.Name)
+ return moduleSymbol.Flags&ast.SymbolFlagsModule != 0 && firstRune == '"'
+}
diff --git a/internal/core/core.go b/internal/core/core.go
index 5456fb3077..811922c0fc 100644
--- a/internal/core/core.go
+++ b/internal/core/core.go
@@ -539,3 +539,21 @@ func levenshteinWithMax(s1 []rune, s2 []rune, maxValue float64) float64 {
func Identity[T any](t T) T {
return t
}
+
+func CheckEachDefined[S any](s []*S, msg string) []*S {
+ for _, value := range s {
+ if value == nil {
+ panic(msg)
+ }
+ }
+ return s
+}
+
+func StripQuotes(name string) string {
+ firstChar, _ := utf8.DecodeRuneInString(name)
+ lastChar, _ := utf8.DecodeLastRuneInString(name)
+ if firstChar == lastChar && (firstChar == '\'' || firstChar == '"' || firstChar == '`') {
+ return name[1 : len(name)-1]
+ }
+ return name
+}
diff --git a/internal/core/set.go b/internal/core/set.go
index 54e261109c..3d094cc2ac 100644
--- a/internal/core/set.go
+++ b/internal/core/set.go
@@ -39,6 +39,15 @@ func (s *Set[T]) Clear() {
clear(s.M)
}
+// Returns true if the key was not already present in the set.
+func (s *Set[T]) AddIfAbsent(key T) bool {
+ if s.Has(key) {
+ return false
+ }
+ s.Add(key)
+ return true
+}
+
func NewSetFromItems[T comparable](items ...T) *Set[T] {
s := &Set[T]{}
for _, item := range items {
diff --git a/internal/ls/completions.go b/internal/ls/completions.go
new file mode 100644
index 0000000000..605ff714f7
--- /dev/null
+++ b/internal/ls/completions.go
@@ -0,0 +1,2321 @@
+package ls
+
+import (
+ "fmt"
+ "maps"
+ "slices"
+ "strings"
+ "sync"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/astnav"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/collections"
+ "github.com/microsoft/typescript-go/internal/compiler"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/jsnum"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/scanner"
+ "github.com/microsoft/typescript-go/internal/stringutil"
+)
+
+func (l *LanguageService) ProvideCompletion(
+ fileName string,
+ position int,
+ context *lsproto.CompletionContext,
+ clientOptions *lsproto.CompletionClientCapabilities,
+ preferences *UserPreferences,
+) *lsproto.CompletionList {
+ program, file := l.getProgramAndFile(fileName)
+ return l.getCompletionsAtPosition(program, file, position, context, preferences, clientOptions)
+}
+
+// !!! figure out other kinds of completion data return
+type completionData struct {
+ // !!!
+ symbols []*ast.Symbol
+ completionKind CompletionKind
+ isInSnippetScope bool
+ // Note that the presence of this alone doesn't mean that we need a conversion. Only do that if the completion is not an ordinary identifier.
+ propertyAccessToConvert *ast.PropertyAccessExpressionNode
+ isNewIdentifierLocation bool
+ location *ast.Node
+ keywordFilters KeywordCompletionFilters
+ literals []literalValue
+ symbolToOriginInfoMap map[ast.SymbolId]*symbolOriginInfo
+ symbolToSortTextMap map[ast.SymbolId]sortText
+ recommendedCompletion *ast.Symbol
+ previousToken *ast.Node
+ contextToken *ast.Node
+ jsxInitializer jsxInitializer
+ insideJsDocTagTypeExpression bool
+ isTypeOnlyLocation bool
+ // In JSX tag name and attribute names, identifiers like "my-tag" or "aria-name" is valid identifier.
+ isJsxIdentifierExpected bool
+ isRightOfOpenTag bool
+ isRightOfDotOrQuestionDot bool
+ importStatementCompletion any // !!!
+ hasUnresolvedAutoImports bool // !!!
+ // flags CompletionInfoFlags // !!!
+ defaultCommitCharacters []string
+}
+
+type importStatementCompletionInfo struct {
+ // !!!
+}
+
+// If we're after the `=` sign but no identifier has been typed yet,
+// value will be `true` but initializer will be `nil`.
+type jsxInitializer struct {
+ isInitializer bool
+ initializer *ast.IdentifierNode
+}
+
+type KeywordCompletionFilters int
+
+const (
+ KeywordCompletionFiltersNone KeywordCompletionFilters = iota // No keywords
+ KeywordCompletionFiltersAll // Every possible kewyord
+ KeywordCompletionFiltersClassElementKeywords // Keywords inside class body
+ KeywordCompletionFiltersInterfaceElementKeywords // Keywords inside interface body
+ KeywordCompletionFiltersConstructorParameterKeywords // Keywords at constructor parameter
+ KeywordCompletionFiltersFunctionLikeBodyKeywords // Keywords at function like body
+ KeywordCompletionFiltersTypeAssertionKeywords
+ KeywordCompletionFiltersTypeKeywords
+ KeywordCompletionFiltersTypeKeyword // Literally just `type`
+ KeywordCompletionFiltersLast = KeywordCompletionFiltersTypeKeyword
+)
+
+type CompletionKind int
+
+const (
+ CompletionKindNone CompletionKind = iota
+ CompletionKindObjectPropertyDeclaration
+ CompletionKindGlobal
+ CompletionKindPropertyAccess
+ CompletionKindMemberLike
+ CompletionKindString
+)
+
+var TriggerCharacters = []string{".", `"`, "'", "`", "/", "@", "<", "#", " "}
+
+// All commit characters, valid when `isNewIdentifierLocation` is false.
+var allCommitCharacters = []string{".", ",", ";"}
+
+// Commit characters valid at expression positions where we could be inside a parameter list.
+var noCommaCommitCharacters = []string{".", ";"}
+
+type sortText string
+
+const (
+ SortTextLocalDeclarationPriority sortText = "10"
+ SortTextLocationPriority sortText = "11"
+ SortTextOptionalMember sortText = "12"
+ SortTextMemberDeclaredBySpreadAssignment sortText = "13"
+ SortTextSuggestedClassMembers sortText = "14"
+ SortTextGlobalsOrKeywords sortText = "15"
+ SortTextAutoImportSuggestions sortText = "16"
+ SortTextClassMemberSnippets sortText = "17"
+ SortTextJavascriptIdentifiers sortText = "18"
+)
+
+func deprecateSortText(original sortText) sortText {
+ return "z" + original
+}
+
+func sortBelow(original sortText) sortText {
+ return original + "1"
+}
+
+type symbolOriginInfoKind int
+
+const (
+ symbolOriginInfoKindThisType symbolOriginInfoKind = 1 << iota
+ symbolOriginInfoKindSymbolMember
+ symbolOriginInfoKindExport
+ symbolOriginInfoKindPromise
+ symbolOriginInfoKindNullable
+ symbolOriginInfoKindResolvedExport
+ symbolOriginInfoKindTypeOnlyAlias
+ symbolOriginInfoKindObjectLiteralMethod
+ symbolOriginInfoKindIgnore
+ symbolOriginInfoKindComputedPropertyName
+
+ symbolOriginInfoKindSymbolMemberNoExport symbolOriginInfoKind = symbolOriginInfoKindSymbolMember
+ symbolOriginInfoKindSymbolMemberExport = symbolOriginInfoKindSymbolMember | symbolOriginInfoKindExport
+)
+
+type symbolOriginInfo struct {
+ kind symbolOriginInfoKind
+ isDefaultExport bool
+ isFromPackageJson bool
+ fileName string
+ data any
+}
+
+func (s *symbolOriginInfo) symbolName() string {
+ switch s.data.(type) {
+ case *symbolOriginInfoExport:
+ return s.data.(*symbolOriginInfoExport).symbolName
+ case *symbolOriginInfoResolvedExport:
+ return s.data.(*symbolOriginInfoResolvedExport).symbolName
+ default:
+ panic(fmt.Sprintf("symbolOriginInfo: unknown data type for symbolName(): %T", s.data))
+ }
+}
+
+type symbolOriginInfoExport struct {
+ symbolName string
+ moduleSymbol *ast.Symbol
+ isDefaultExport bool
+ exporName string
+ // exportMapKey ExportMapInfoKey // !!!
+}
+
+func (s *symbolOriginInfo) asExport() *symbolOriginInfoExport {
+ return s.data.(*symbolOriginInfoExport)
+}
+
+type symbolOriginInfoResolvedExport struct {
+ symbolName string
+ moduleSymbol *ast.Symbol
+ exportName string
+ // exportMapKey ExportMapInfoKey // !!!
+ moduleSpecifier string
+}
+
+func (s *symbolOriginInfo) asResolvedExport() *symbolOriginInfoResolvedExport {
+ return s.data.(*symbolOriginInfoResolvedExport)
+}
+
+type symbolOriginInfoObjectLiteralMethod struct {
+ insertText string
+ labelDetails *lsproto.CompletionItemLabelDetails
+ isSnippet bool
+}
+
+func (s *symbolOriginInfo) asObjectLiteralMethod() *symbolOriginInfoObjectLiteralMethod {
+ return s.data.(*symbolOriginInfoObjectLiteralMethod)
+}
+
+// Special values for `CompletionInfo['source']` used to disambiguate
+// completion items with the same `name`. (Each completion item must
+// have a unique name/source combination, because those two fields
+// comprise `CompletionEntryIdentifier` in `getCompletionEntryDetails`.
+//
+// When the completion item is an auto-import suggestion, the source
+// is the module specifier of the suggestion. To avoid collisions,
+// the values here should not be a module specifier we would ever
+// generate for an auto-import.
+type completionSource string
+
+const (
+ // Completions that require `this.` insertion text.
+ completionSourceThisProperty completionSource = "ThisProperty/"
+ // Auto-import that comes attached to a class member snippet.
+ completionSourceClassMemberSnippet completionSource = "ClassMemberSnippet/"
+ // A type-only import that needs to be promoted in order to be used at the completion location.
+ completionSourceTypeOnlyAlias completionSource = "TypeOnlyAlias/"
+ // Auto-import that comes attached to an object literal method snippet.
+ completionSourceObjectLiteralMethodSnippet completionSource = "ObjectLiteralMethodSnippet/"
+ // Case completions for switch statements.
+ completionSourceSwitchCases completionSource = "SwitchCases/"
+ // Completions for an object literal expression.
+ completionSourceObjectLiteralMemberWithComma completionSource = "ObjectLiteralMemberWithComma/"
+)
+
+// Value is set to false for global variables or completions from external module exports,
+// true otherwise.
+type uniqueNamesMap = map[string]bool
+
+type literalValue any // string | jsnum.Number | PseudoBigInt
+
+func (l *LanguageService) getCompletionsAtPosition(
+ program *compiler.Program,
+ file *ast.SourceFile,
+ position int,
+ context *lsproto.CompletionContext,
+ preferences *UserPreferences,
+ clientOptions *lsproto.CompletionClientCapabilities,
+) *lsproto.CompletionList {
+ previousToken, _ := getRelevantTokens(position, file)
+ if context.TriggerCharacter != nil && !isInString(file, position, previousToken) && !isValidTrigger(file, *context.TriggerCharacter, previousToken, position) {
+ return nil
+ }
+
+ if context.TriggerCharacter != nil && *context.TriggerCharacter == " " {
+ // `isValidTrigger` ensures we are at `import |`
+ if ptrIsTrue(preferences.IncludeCompletionsForImportStatements) {
+ // !!! isMemberCompletion
+ return &lsproto.CompletionList{
+ IsIncomplete: true,
+ ItemDefaults: &lsproto.CompletionItemDefaults{ // !!! do we need this if no entries? also, check if client supports item defaults
+ CommitCharacters: ptrTo(getDefaultCommitCharacters(true /*isNewIdentifierLocation*/)),
+ },
+ }
+ }
+ return nil
+ }
+
+ compilerOptions := program.GetCompilerOptions()
+
+ // !!! see if incomplete completion list and continue or clean
+
+ // !!! string literal completions
+
+ // !!! label completions
+
+ completionData := getCompletionData(program, file, position, preferences)
+ if completionData == nil {
+ return nil
+ }
+
+ // switch completionData.Kind // !!! other data cases
+ // !!! transform data into completion list
+
+ response := l.completionInfoFromData(
+ file,
+ program,
+ compilerOptions,
+ completionData,
+ preferences,
+ position,
+ clientOptions,
+ )
+ // !!! check if response is incomplete
+ return response
+}
+
+func getCompletionData(program *compiler.Program, file *ast.SourceFile, position int, preferences *UserPreferences) *completionData {
+ typeChecker := program.GetTypeChecker()
+ inCheckedFile := isCheckedFile(file, program.GetCompilerOptions())
+
+ currentToken := astnav.GetTokenAtPosition(file, position)
+
+ insideComment := isInComment(file, position, currentToken)
+
+ insideJsDocTagTypeExpression := false
+ insideJsDocImportTag := false
+ isInSnippetScope := false
+ if insideComment != nil {
+ // !!! jsdoc
+ }
+
+ // The decision to provide completion depends on the contextToken, which is determined through the previousToken.
+ // Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
+ // isJSOnlyLocation := !insideJsDocTagTypeExpression && !insideJsDocImportTag && ast.IsSourceFileJS(file)
+ previousToken, contextToken := getRelevantTokens(position, file)
+
+ // Find the node where completion is requested on.
+ // Also determine whether we are trying to complete with members of that node
+ // or attributes of a JSX tag.
+ node := currentToken
+ var propertyAccessToConvert *ast.PropertyAccessExpressionNode
+ isRightOfDot := false
+ isRightOfQuestionDot := false
+ isRightOfOpenTag := false
+ isStartingCloseTag := false
+ var jsxInitializer jsxInitializer
+ isJsxIdentifierExpected := false
+ var importStatementCompletion *importStatementCompletionInfo
+ location := astnav.GetTouchingPropertyName(file, position)
+ keywordFilters := KeywordCompletionFiltersNone
+ isNewIdentifierLocation := false
+ // !!!
+ // flags := CompletionInfoFlagsNone
+ var defaultCommitCharacters []string
+
+ if contextToken != nil {
+ // !!! import completions
+
+ parent := contextToken.Parent
+ if contextToken.Kind == ast.KindDotToken || contextToken.Kind == ast.KindQuestionDotToken {
+ isRightOfDot = contextToken.Kind == ast.KindDotToken
+ isRightOfQuestionDot = contextToken.Kind == ast.KindQuestionDotToken
+ switch parent.Kind {
+ case ast.KindPropertyAccessExpression:
+ propertyAccessToConvert = parent
+ node = propertyAccessToConvert.Expression()
+ leftMostAccessExpression := ast.GetLeftmostAccessExpression(parent)
+ if ast.NodeIsMissing(leftMostAccessExpression) ||
+ ((ast.IsCallExpression(node) || ast.IsFunctionLike(node)) &&
+ node.End() == contextToken.Pos() &&
+ getLastChild(node, file).Kind != ast.KindCloseParenToken) {
+ // This is likely dot from incorrectly parsed expression and user is starting to write spread
+ // eg: Math.min(./**/)
+ // const x = function (./**/) {}
+ // ({./**/})
+ return nil
+ }
+ case ast.KindQualifiedName:
+ node = parent.AsQualifiedName().Left
+ case ast.KindModuleDeclaration:
+ node = parent.Name()
+ case ast.KindImportType:
+ node = parent
+ case ast.KindMetaProperty:
+ node = getFirstToken(parent, file)
+ if node.Kind != ast.KindImportKeyword && node.Kind != ast.KindNewKeyword {
+ panic("Unexpected token kind: " + node.Kind.String())
+ }
+ default:
+ // There is nothing that precedes the dot, so this likely just a stray character
+ // or leading into a '...' token. Just bail out instead.
+ return nil
+ }
+ } else { // !!! else if (!importStatementCompletion)
+ //
+ // If the tagname is a property access expression, we will then walk up to the top most of property access expression.
+ // Then, try to get a JSX container and its associated attributes type.
+ if parent != nil && parent.Kind == ast.KindPropertyAccessExpression {
+ contextToken = parent
+ parent = parent.Parent
+ }
+
+ // Fix location
+ if parent == location {
+ switch currentToken.Kind {
+ case ast.KindGreaterThanToken:
+ if parent.Kind == ast.KindJsxElement || parent.Kind == ast.KindJsxOpeningElement {
+ location = currentToken
+ }
+ case ast.KindSlashToken:
+ if parent.Kind == ast.KindJsxSelfClosingElement {
+ location = currentToken
+ }
+ }
+ }
+
+ switch parent.Kind {
+ case ast.KindJsxClosingElement:
+ if contextToken.Kind == ast.KindSlashToken {
+ isStartingCloseTag = true
+ location = contextToken
+ }
+ case ast.KindBinaryExpression:
+ if !binaryExpressionMayBeOpenTag(parent.AsBinaryExpression()) {
+ break
+ }
+ fallthrough
+ case ast.KindJsxSelfClosingElement, ast.KindJsxElement, ast.KindJsxOpeningElement:
+ isJsxIdentifierExpected = true
+ if contextToken.Kind == ast.KindLessThanToken {
+ isRightOfOpenTag = true
+ location = contextToken
+ }
+ case ast.KindJsxExpression, ast.KindJsxSpreadAttribute:
+ // First case is for `` or ``,
+ // `parent` will be `{true}` and `previousToken` will be `}`.
+ // Second case is for ``.
+ // Second case must not match for ``.
+ if previousToken.Kind == ast.KindCloseBraceToken ||
+ previousToken.Kind == ast.KindIdentifier && previousToken.Parent.Kind == ast.KindJsxAttribute {
+ isJsxIdentifierExpected = true
+ }
+ case ast.KindJsxAttribute:
+ // For ``, `parent` will be JsxAttribute and `previousToken` will be its initializer.
+ if parent.Initializer() == previousToken && previousToken.End() < position {
+ isJsxIdentifierExpected = true
+ } else {
+ switch previousToken.Kind {
+ case ast.KindEqualsToken:
+ jsxInitializer.isInitializer = true
+ case ast.KindIdentifier:
+ isJsxIdentifierExpected = true
+ // For `` we don't want to treat this as a jsx inializer, instead it's the attribute name.
+ if parent != previousToken.Parent &&
+ parent.Initializer() == nil &&
+ findChildOfKind(parent, ast.KindEqualsToken, file) != nil {
+ jsxInitializer.initializer = previousToken
+ }
+ }
+ }
+ }
+ }
+ }
+
+ completionKind := CompletionKindNone
+ hasUnresolvedAutoImports := false
+ // This also gets mutated in nested-functions after the return
+ var symbols []*ast.Symbol
+ symbolToOriginInfoMap := map[ast.SymbolId]*symbolOriginInfo{}
+ symbolToSortTextMap := map[ast.SymbolId]sortText{}
+ // var importSpecifierResolver any // !!! import
+ var seenPropertySymbols core.Set[ast.SymbolId]
+ isTypeOnlyLocation := insideJsDocTagTypeExpression || insideJsDocImportTag ||
+ importStatementCompletion != nil && ast.IsTypeOnlyImportOrExportDeclaration(location.Parent) ||
+ !isContextTokenValueLocation(contextToken) &&
+ (isPossiblyTypeArgumentPosition(contextToken, file, typeChecker) ||
+ ast.IsPartOfTypeNode(location) ||
+ isContextTokenTypeLocation(contextToken))
+ // var getModuleSpecifierResolutionHost any // !!! auto import
+
+ addSymbolOriginInfo := func(symbol *ast.Symbol, insertQuestionDot bool, insertAwait bool) {
+ symbolId := ast.GetSymbolId(symbol)
+ if insertAwait && seenPropertySymbols.AddIfAbsent(symbolId) {
+ symbolToOriginInfoMap[symbolId] = &symbolOriginInfo{kind: getNullableSymbolOriginInfoKind(symbolOriginInfoKindPromise, insertQuestionDot)}
+ } else if insertQuestionDot {
+ symbolToOriginInfoMap[symbolId] = &symbolOriginInfo{kind: symbolOriginInfoKindNullable}
+ }
+ }
+
+ addSymbolSortInfo := func(symbol *ast.Symbol) {
+ symbolId := ast.GetSymbolId(symbol)
+ if isStaticProperty(symbol) {
+ symbolToSortTextMap[symbolId] = SortTextLocalDeclarationPriority
+ }
+ }
+
+ addPropertySymbol := func(symbol *ast.Symbol, insertAwait bool, insertQuestionDot bool) {
+ // For a computed property with an accessible name like `Symbol.iterator`,
+ // we'll add a completion for the *name* `Symbol` instead of for the property.
+ // If this is e.g. [Symbol.iterator], add a completion for `Symbol`.
+ computedPropertyName := core.FirstNonNil(symbol.Declarations, func(decl *ast.Node) *ast.Node {
+ name := ast.GetNameOfDeclaration(decl)
+ if name != nil && name.Kind == ast.KindComputedPropertyName {
+ return name
+ }
+ return nil
+ })
+
+ if computedPropertyName != nil {
+ leftMostName := getLeftMostName(computedPropertyName.Expression()) // The completion is for `Symbol`, not `iterator`.
+ var nameSymbol *ast.Symbol
+ if leftMostName != nil {
+ nameSymbol = typeChecker.GetSymbolAtLocation(leftMostName)
+ }
+ // If this is nested like for `namespace N { export const sym = Symbol(); }`, we'll add the completion for `N`.
+ var firstAccessibleSymbol *ast.Symbol
+ if nameSymbol != nil {
+ firstAccessibleSymbol = getFirstSymbolInChain(nameSymbol, contextToken, typeChecker)
+ }
+ var firstAccessibleSymbolId ast.SymbolId
+ if firstAccessibleSymbol != nil {
+ firstAccessibleSymbolId = ast.GetSymbolId(firstAccessibleSymbol)
+ }
+ if firstAccessibleSymbolId != 0 && seenPropertySymbols.AddIfAbsent(firstAccessibleSymbolId) {
+ symbols = append(symbols, firstAccessibleSymbol)
+ moduleSymbol := firstAccessibleSymbol.Parent
+ if moduleSymbol == nil ||
+ !checker.IsExternalModuleSymbol(moduleSymbol) ||
+ typeChecker.TryGetMemberInModuleExportsAndProperties(firstAccessibleSymbol.Name, moduleSymbol) != firstAccessibleSymbol {
+ symbolToOriginInfoMap[ast.GetSymbolId(symbol)] = &symbolOriginInfo{kind: getNullableSymbolOriginInfoKind(symbolOriginInfoKindSymbolMemberNoExport, insertQuestionDot)}
+ } else {
+ // !!! imports
+ // var fileName string
+ // if tspath.IsExternalModuleNameRelative(core.StripQuotes(moduleSymbol.Name)) {
+ // fileName = ast.GetSourceFileOfModule(moduleSymbol).FileName()
+ // }
+ // if importSpecifierResolver == nil {
+ // importSpecifierResolver ||= codefix.createImportSpecifierResolver(sourceFile, program, host, preferences))
+ // }
+ // const { moduleSpecifier } = importSpecifier.getModuleSpecifierForBestExportInfo(
+ // [{
+ // exportKind: ExportKind.Named,
+ // moduleFileName: fileName,
+ // isFromPackageJson: false,
+ // moduleSymbol,
+ // symbol: firstAccessibleSymbol,
+ // targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags,
+ // }],
+ // position,
+ // isValidTypeOnlyAliasUseSite(location),
+ // ) || {};
+ // if (moduleSpecifier) {
+ // const origin: SymbolOriginInfoResolvedExport = {
+ // kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport),
+ // moduleSymbol,
+ // isDefaultExport: false,
+ // symbolName: firstAccessibleSymbol.name,
+ // exportName: firstAccessibleSymbol.name,
+ // fileName,
+ // moduleSpecifier,
+ // };
+ // symbolToOriginInfoMap[index] = origin;
+ // }
+ }
+ } else if firstAccessibleSymbolId == 0 || !seenPropertySymbols.Has(firstAccessibleSymbolId) {
+ symbols = append(symbols, symbol)
+ addSymbolOriginInfo(symbol, insertQuestionDot, insertAwait)
+ addSymbolSortInfo(symbol)
+ }
+ } else {
+ symbols = append(symbols, symbol)
+ addSymbolOriginInfo(symbol, insertQuestionDot, insertAwait)
+ addSymbolSortInfo(symbol)
+ }
+ }
+
+ addTypeProperties := func(t *checker.Type, insertAwait bool, insertQuestionDot bool) {
+ if typeChecker.GetStringIndexType(t) != nil {
+ isNewIdentifierLocation = true
+ defaultCommitCharacters = []string{}
+ }
+ if isRightOfQuestionDot && len(typeChecker.GetCallSignatures(t)) != 0 {
+ isNewIdentifierLocation = true
+ if defaultCommitCharacters == nil {
+ defaultCommitCharacters = slices.Clone(allCommitCharacters) // Only invalid commit character here would be `(`.
+ }
+ }
+
+ var propertyAccess *ast.Node
+ if node.Kind == ast.KindImportType {
+ propertyAccess = node
+ } else {
+ propertyAccess = node.Parent
+ }
+
+ if inCheckedFile {
+ for _, symbol := range typeChecker.GetApparentProperties(t) {
+ if typeChecker.IsValidPropertyAccessForCompletions(propertyAccess, t, symbol) {
+ addPropertySymbol(symbol, false /*insertAwait*/, insertQuestionDot)
+ }
+ }
+ } else {
+ // In javascript files, for union types, we don't just get the members that
+ // the individual types have in common, we also include all the members that
+ // each individual type has. This is because we're going to add all identifiers
+ // anyways. So we might as well elevate the members that were at least part
+ // of the individual types to a higher status since we know what they are.
+ for _, symbol := range getPropertiesForCompletion(t, typeChecker) {
+ if typeChecker.IsValidPropertyAccessForCompletions(propertyAccess, t, symbol) {
+ symbols = append(symbols, symbol)
+ }
+ }
+ }
+
+ if insertAwait {
+ promiseType := typeChecker.GetPromisedTypeOfPromise(t)
+ if promiseType != nil {
+ for _, symbol := range typeChecker.GetApparentProperties(promiseType) {
+ if typeChecker.IsValidPropertyAccessForCompletions(propertyAccess, promiseType, symbol) {
+ addPropertySymbol(symbol, true /*insertAwait*/, insertQuestionDot)
+ }
+ }
+ }
+ }
+ }
+
+ getTypeScriptMemberSymbols := func() {
+ // Right of dot member completion list
+ completionKind = CompletionKindPropertyAccess
+
+ // Since this is qualified name check it's a type node location
+ isImportType := ast.IsLiteralImportTypeNode(node)
+ isTypeLocation := (isImportType && !node.AsImportTypeNode().IsTypeOf) ||
+ ast.IsPartOfTypeNode(node.Parent) ||
+ isPossiblyTypeArgumentPosition(contextToken, file, typeChecker)
+ isRhsOfImportDeclaration := isInRightSideOfInternalImportEqualsDeclaration(node)
+ if ast.IsEntityName(node) || isImportType || ast.IsPropertyAccessExpression(node) {
+ isNamespaceName := ast.IsModuleDeclaration(node.Parent)
+ if isNamespaceName {
+ isNewIdentifierLocation = true
+ defaultCommitCharacters = []string{}
+ }
+ symbol := typeChecker.GetSymbolAtLocation(node)
+ if symbol != nil {
+ symbol := checker.SkipAlias(symbol, typeChecker)
+ if symbol.Flags&(ast.SymbolFlagsModule|ast.SymbolFlagsEnum) != 0 {
+ var valueAccessNode *ast.Node
+ if isImportType {
+ valueAccessNode = node
+ } else {
+ valueAccessNode = node.Parent
+ }
+ // Extract module or enum members
+ exportedSymbols := typeChecker.GetExportsOfModule(symbol)
+ for _, exportedSymbol := range exportedSymbols {
+ if exportedSymbol == nil {
+ panic("getExporsOfModule() should all be defined")
+ }
+ isValidValueAccess := func(s *ast.Symbol) bool {
+ return typeChecker.IsValidPropertyAccess(valueAccessNode, s.Name)
+ }
+ isValidTypeAccess := func(s *ast.Symbol) bool {
+ return symbolCanBeReferencedAtTypeLocation(s, typeChecker, core.Set[ast.SymbolId]{})
+ }
+ var isValidAccess bool
+ if isNamespaceName {
+ // At `namespace N.M/**/`, if this is the only declaration of `M`, don't include `M` as a completion.
+ isValidAccess = exportedSymbol.Flags&ast.SymbolFlagsNamespace != 0 &&
+ !core.Every(exportedSymbol.Declarations, func(declaration *ast.Declaration) bool {
+ return declaration.Parent == node.Parent
+ })
+ } else if isRhsOfImportDeclaration {
+ // Any kind is allowed when dotting off namespace in internal import equals declaration
+ isValidAccess = isValidTypeAccess(exportedSymbol) || isValidValueAccess(exportedSymbol)
+ } else if isTypeLocation || insideJsDocTagTypeExpression {
+ isValidAccess = isValidTypeAccess(exportedSymbol)
+ } else {
+ isValidAccess = isValidValueAccess(exportedSymbol)
+ }
+ if isValidAccess {
+ symbols = append(symbols, exportedSymbol)
+ }
+ }
+
+ // If the module is merged with a value, we must get the type of the class and add its properties (for inherited static methods).
+ if !isTypeLocation && !insideJsDocTagTypeExpression &&
+ core.Some(
+ symbol.Declarations,
+ func(decl *ast.Declaration) bool {
+ return decl.Kind != ast.KindSourceFile && decl.Kind != ast.KindModuleDeclaration && decl.Kind != ast.KindEnumDeclaration
+ }) {
+ t := typeChecker.GetNonOptionalType(typeChecker.GetTypeOfSymbolAtLocation(symbol, node))
+ insertQuestionDot := false
+ if typeChecker.IsNullableType(t) {
+ canCorrectToQuestionDot := isRightOfDot && !isRightOfQuestionDot &&
+ !ptrIsFalse(preferences.IncludeAutomaticOptionalChainCompletions)
+ if canCorrectToQuestionDot || isRightOfQuestionDot {
+ t = typeChecker.GetNonNullableType(t)
+ if canCorrectToQuestionDot {
+ insertQuestionDot = true
+ }
+ }
+ }
+ addTypeProperties(t, node.Flags&ast.NodeFlagsAwaitContext != 0, insertQuestionDot)
+ }
+
+ return
+ }
+ }
+ }
+
+ if !isTypeLocation || checker.IsInTypeQuery(node) {
+ // microsoft/TypeScript#39946. Pulling on the type of a node inside of a function with a contextual `this` parameter can result in a circularity
+ // if the `node` is part of the exprssion of a `yield` or `return`. This circularity doesn't exist at compile time because
+ // we will check (and cache) the type of `this` *before* checking the type of the node.
+ typeChecker.TryGetThisTypeAtEx(node, false /*includeGlobalThis*/, nil)
+ t := typeChecker.GetNonOptionalType(typeChecker.GetTypeAtLocation(node))
+
+ if !isTypeLocation {
+ insertQuestionDot := false
+ if typeChecker.IsNullableType(t) {
+ canCorrectToQuestionDot := isRightOfDot && !isRightOfQuestionDot &&
+ !ptrIsFalse(preferences.IncludeAutomaticOptionalChainCompletions)
+
+ if canCorrectToQuestionDot || isRightOfQuestionDot {
+ t = typeChecker.GetNonNullableType(t)
+ if canCorrectToQuestionDot {
+ insertQuestionDot = true
+ }
+ }
+ }
+ addTypeProperties(t, node.Flags&ast.NodeFlagsAwaitContext != 0, insertQuestionDot)
+ } else {
+ addTypeProperties(typeChecker.GetNonNullableType(t), false /*insertAwait*/, false /*insertQuestionDot*/)
+ }
+ }
+ }
+
+ if isRightOfDot || isRightOfQuestionDot {
+ getTypeScriptMemberSymbols()
+ } else if isRightOfOpenTag {
+ // !!! jsx completions
+ } else if isStartingCloseTag {
+ // !!! jsx completions
+ } else {
+ // For JavaScript or TypeScript, if we're not after a dot, then just try to get the
+ // global symbols in scope. These results should be valid for either language as
+ // the set of symbols that can be referenced from this location.
+ // !!! global completions
+ }
+
+ var contextualType *checker.Type
+ if previousToken != nil {
+ contextualType = getContextualType(previousToken, position, file, typeChecker)
+ }
+
+ // exclude literal suggestions after microsoft/TypeScript#51667) and after closing quote (microsoft/TypeScript#52675)
+ // for strings getStringLiteralCompletions handles completions
+ isLiteralExpected := !ast.IsStringLiteralLike(previousToken) && !isJsxIdentifierExpected
+ var literals []literalValue
+ if isLiteralExpected {
+ var types []*checker.Type
+ if contextualType != nil && contextualType.IsUnion() {
+ types = contextualType.Types()
+ } else if contextualType != nil {
+ types = []*checker.Type{contextualType}
+ }
+ literals = core.MapNonNil(types, func(t *checker.Type) literalValue {
+ if isLiteral(t) && !t.IsEnumLiteral() {
+ return t.AsLiteralType().Value()
+ }
+ return nil
+ })
+ }
+
+ var recommendedCompletion *ast.Symbol
+ if previousToken != nil && contextualType != nil {
+ recommendedCompletion = getRecommendedCompletion(previousToken, contextualType, typeChecker)
+ }
+
+ if defaultCommitCharacters == nil {
+ defaultCommitCharacters = getDefaultCommitCharacters(isNewIdentifierLocation)
+ }
+
+ return &completionData{
+ symbols: symbols,
+ completionKind: completionKind,
+ isInSnippetScope: isInSnippetScope,
+ propertyAccessToConvert: propertyAccessToConvert,
+ isNewIdentifierLocation: isNewIdentifierLocation,
+ location: location,
+ keywordFilters: keywordFilters,
+ literals: literals,
+ symbolToOriginInfoMap: symbolToOriginInfoMap,
+ symbolToSortTextMap: symbolToSortTextMap,
+ recommendedCompletion: recommendedCompletion,
+ previousToken: previousToken,
+ contextToken: contextToken,
+ jsxInitializer: jsxInitializer,
+ insideJsDocTagTypeExpression: insideJsDocTagTypeExpression,
+ isTypeOnlyLocation: isTypeOnlyLocation,
+ isJsxIdentifierExpected: isJsxIdentifierExpected,
+ isRightOfOpenTag: isRightOfOpenTag,
+ isRightOfDotOrQuestionDot: isRightOfDot || isRightOfQuestionDot,
+ importStatementCompletion: importStatementCompletion,
+ hasUnresolvedAutoImports: hasUnresolvedAutoImports,
+ defaultCommitCharacters: defaultCommitCharacters,
+ }
+}
+
+func getDefaultCommitCharacters(isNewIdentifierLocation bool) []string {
+ if isNewIdentifierLocation {
+ return []string{}
+ }
+ return slices.Clone(allCommitCharacters)
+}
+
+func (l *LanguageService) completionInfoFromData(
+ file *ast.SourceFile,
+ program *compiler.Program,
+ compilerOptions *core.CompilerOptions,
+ data *completionData,
+ preferences *UserPreferences,
+ position int,
+ clientOptions *lsproto.CompletionClientCapabilities,
+) *lsproto.CompletionList {
+ keywordFilters := data.keywordFilters
+ symbols := data.symbols
+ isNewIdentifierLocation := data.isNewIdentifierLocation
+ contextToken := data.contextToken
+ literals := data.literals
+
+ // Verify if the file is JSX language variant
+ if ast.GetLanguageVariant(file.ScriptKind) == core.LanguageVariantJSX {
+ // !!! jsx
+ return nil
+ }
+
+ // When the completion is for the expression of a case clause (e.g. `case |`),
+ // filter literals & enum symbols whose values are already present in existing case clauses.
+ caseClause := ast.FindAncestor(contextToken, ast.IsCaseClause)
+ if caseClause != nil &&
+ (contextToken.Kind == ast.KindCaseKeyword ||
+ ast.IsNodeDescendantOf(contextToken, caseClause.Expression())) {
+ // !!! switch completions
+ }
+
+ isChecked := isCheckedFile(file, compilerOptions)
+ if isChecked && !isNewIdentifierLocation && len(symbols) == 0 && keywordFilters == KeywordCompletionFiltersNone {
+ return nil
+ }
+
+ uniqueNames, sortedEntries := l.getCompletionEntriesFromSymbols(
+ data,
+ nil, /*replacementToken*/
+ position,
+ file,
+ program,
+ compilerOptions.GetEmitScriptTarget(),
+ preferences,
+ compilerOptions,
+ clientOptions,
+ )
+
+ if data.keywordFilters != KeywordCompletionFiltersNone {
+ keywordCompletions := getKeywordCompletions(
+ data.keywordFilters,
+ !data.insideJsDocTagTypeExpression && ast.IsSourceFileJS(file))
+ for _, keywordEntry := range keywordCompletions {
+ if data.isTypeOnlyLocation && isTypeKeyword(scanner.StringToToken(keywordEntry.Label)) ||
+ !data.isTypeOnlyLocation && isContextualKeywordInAutoImportableExpressionSpace(keywordEntry.Label) ||
+ !uniqueNames.Has(keywordEntry.Label) {
+ uniqueNames.Add(keywordEntry.Label)
+ sortedEntries = core.InsertSorted(sortedEntries, keywordEntry, compareCompletionEntries)
+ }
+ }
+ }
+
+ for _, keywordEntry := range getContextualKeywords(file, contextToken, position) {
+ if !uniqueNames.Has(keywordEntry.Label) {
+ uniqueNames.Add(keywordEntry.Label)
+ sortedEntries = core.InsertSorted(sortedEntries, keywordEntry, compareCompletionEntries)
+ }
+ }
+
+ for _, literal := range literals {
+ literalEntry := createCompletionItemForLiteral(file, preferences, literal)
+ uniqueNames.Add(literalEntry.Label)
+ sortedEntries = core.InsertSorted(sortedEntries, literalEntry, compareCompletionEntries)
+ }
+
+ if !isChecked {
+ sortedEntries = getJSCompletionEntries(
+ file,
+ position,
+ uniqueNames,
+ compilerOptions.GetEmitScriptTarget(),
+ sortedEntries,
+ )
+ }
+
+ // !!! exhaustive case completions
+
+ var defaultCommitCharacters *[]string
+ if supportsDefaultCommitCharacters(clientOptions) && ptrIsTrue(clientOptions.CompletionItem.CommitCharactersSupport) {
+ defaultCommitCharacters = &data.defaultCommitCharacters
+ }
+
+ var itemDefaults *lsproto.CompletionItemDefaults
+ if defaultCommitCharacters != nil {
+ itemDefaults = &lsproto.CompletionItemDefaults{
+ CommitCharacters: defaultCommitCharacters,
+ }
+ }
+
+ // !!! port behavior of other strada fields of CompletionInfo that are non-LSP
+ return &lsproto.CompletionList{
+ IsIncomplete: data.hasUnresolvedAutoImports,
+ ItemDefaults: itemDefaults,
+ Items: sortedEntries,
+ }
+}
+
+func (l *LanguageService) getCompletionEntriesFromSymbols(
+ data *completionData,
+ replacementToken *ast.Node,
+ position int,
+ file *ast.SourceFile,
+ program *compiler.Program,
+ target core.ScriptTarget,
+ preferences *UserPreferences,
+ compilerOptions *core.CompilerOptions,
+ clientOptions *lsproto.CompletionClientCapabilities,
+) (uniqueNames *core.Set[string], sortedEntries []*lsproto.CompletionItem) {
+ closestSymbolDeclaration := getClosestSymbolDeclaration(data.contextToken, data.location)
+ useSemicolons := probablyUsesSemicolons(file)
+ typeChecker := program.GetTypeChecker()
+ // Tracks unique names.
+ // Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
+ // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
+ // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name.
+ uniques := make(uniqueNamesMap)
+ for _, symbol := range data.symbols {
+ symbolId := ast.GetSymbolId(symbol)
+ origin := data.symbolToOriginInfoMap[symbolId]
+ name, needsConvertPropertyAccess := getCompletionEntryDisplayNameForSymbol(
+ symbol,
+ target,
+ origin,
+ data.completionKind,
+ data.isJsxIdentifierExpected,
+ )
+ if name == "" ||
+ uniques[name] && (origin == nil || !originIsObjectLiteralMethod(origin)) ||
+ data.completionKind == CompletionKindGlobal &&
+ !shouldIncludeSymbol(symbol, data, closestSymbolDeclaration, file, typeChecker, compilerOptions) {
+ continue
+ }
+
+ // When in a value location in a JS file, ignore symbols that definitely seem to be type-only.
+ if !data.isTypeOnlyLocation && ast.IsSourceFileJS(file) && symbolAppearsToBeTypeOnly(symbol, typeChecker) {
+ continue
+ }
+
+ originalSortText := data.symbolToSortTextMap[ast.GetSymbolId(symbol)]
+ if originalSortText == "" {
+ originalSortText = SortTextLocationPriority
+ }
+
+ var sortText sortText
+ if isDeprecated(symbol, typeChecker) {
+ sortText = deprecateSortText(originalSortText)
+ } else {
+ sortText = originalSortText
+ }
+ entry := l.createCompletionItem(
+ symbol,
+ sortText,
+ replacementToken,
+ data,
+ position,
+ file,
+ program,
+ name,
+ needsConvertPropertyAccess,
+ origin,
+ useSemicolons,
+ compilerOptions,
+ preferences,
+ clientOptions,
+ )
+ if entry == nil {
+ continue
+ }
+
+ /** True for locals; false for globals, module exports from other files, `this.` completions. */
+ shouldShadowLaterSymbols := (origin == nil || originIsTypeOnlyAlias(origin)) &&
+ !(symbol.Parent == nil &&
+ !core.Some(symbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) == file }))
+ uniques[name] = shouldShadowLaterSymbols
+ sortedEntries = core.InsertSorted(sortedEntries, entry, compareCompletionEntries)
+ }
+
+ uniqueSet := core.NewSetWithSizeHint[string](len(uniques))
+ for name := range maps.Keys(uniques) {
+ uniqueSet.Add(name)
+ }
+ return uniqueSet, sortedEntries
+}
+
+func completionNameForLiteral(
+ file *ast.SourceFile,
+ preferences *UserPreferences,
+ literal literalValue,
+) string {
+ switch literal := literal.(type) {
+ case string:
+ return quote(file, preferences, literal)
+ case jsnum.Number:
+ name, _ := core.StringifyJson(literal, "" /*prefix*/, "" /*suffix*/)
+ return name
+ case jsnum.PseudoBigInt:
+ return literal.String() + "n"
+ }
+ panic(fmt.Sprintf("Unhandled literal value: %v", literal))
+}
+
+func createCompletionItemForLiteral(
+ file *ast.SourceFile,
+ preferences *UserPreferences,
+ literal literalValue,
+) *lsproto.CompletionItem {
+ return &lsproto.CompletionItem{
+ Label: completionNameForLiteral(file, preferences, literal),
+ Kind: ptrTo(lsproto.CompletionItemKindConstant),
+ SortText: ptrTo(string(SortTextLocationPriority)),
+ CommitCharacters: ptrTo([]string{}),
+ }
+}
+
+func (l *LanguageService) createCompletionItem(
+ symbol *ast.Symbol,
+ sortText sortText,
+ replacementToken *ast.Node,
+ data *completionData,
+ position int,
+ file *ast.SourceFile,
+ program *compiler.Program,
+ name string,
+ needsConvertPropertyAccess bool,
+ origin *symbolOriginInfo,
+ useSemicolons bool,
+ compilerOptions *core.CompilerOptions,
+ preferences *UserPreferences,
+ clientOptions *lsproto.CompletionClientCapabilities,
+) *lsproto.CompletionItem {
+ contextToken := data.contextToken
+ var insertText string
+ var filterText string
+ replacementSpan := l.getReplacementRangeForContextToken(file, replacementToken, position)
+ var isSnippet, hasAction bool
+ source := getSourceFromOrigin(origin)
+ var labelDetails *lsproto.CompletionItemLabelDetails
+
+ typeChecker := program.GetTypeChecker()
+ insertQuestionDot := originIsNullableMember(origin)
+ useBraces := originIsSymbolMember(origin) || needsConvertPropertyAccess
+ if originIsThisType(origin) {
+ if needsConvertPropertyAccess {
+ insertText = fmt.Sprintf(
+ "this%s[%s]",
+ core.IfElse(insertQuestionDot, "?.", ""),
+ quotePropertyName(file, preferences, name))
+ } else {
+ insertText = fmt.Sprintf(
+ "this%s%s",
+ core.IfElse(insertQuestionDot, "?.", ""),
+ name)
+ }
+ } else if data.propertyAccessToConvert != nil && (useBraces || insertQuestionDot) {
+ // We should only have needsConvertPropertyAccess if there's a property access to convert. But see microsoft/TypeScript#21790.
+ // Somehow there was a global with a non-identifier name. Hopefully someone will complain about getting a "foo bar" global completion and provide a repro.
+ if useBraces {
+ if needsConvertPropertyAccess {
+ insertText = fmt.Sprintf("[%s]", quotePropertyName(file, preferences, name))
+ } else {
+ insertText = fmt.Sprintf("[%s]", name)
+ }
+ } else {
+ insertText = name
+ }
+
+ if insertQuestionDot || data.propertyAccessToConvert.AsPropertyAccessExpression().QuestionDotToken != nil {
+ insertText = "?." + insertText
+ }
+
+ dot := findChildOfKind(data.propertyAccessToConvert, ast.KindDotToken, file)
+ if dot == nil {
+ dot = findChildOfKind(data.propertyAccessToConvert, ast.KindQuestionDotToken, file)
+ }
+
+ if dot == nil {
+ return nil
+ }
+
+ // If the text after the '.' starts with this name, write over it. Else, add new text.
+ var end int
+ if strings.HasPrefix(name, data.propertyAccessToConvert.Name().Text()) {
+ end = data.propertyAccessToConvert.End()
+ } else {
+ end = dot.End()
+ }
+ replacementSpan = l.createLspRangeFromBounds(astnav.GetStartOfNode(dot, file, false /*includeJSDoc*/), end, file)
+ }
+
+ if data.jsxInitializer.isInitializer {
+ if insertText == "" {
+ insertText = name
+ }
+ insertText = fmt.Sprintf("{%s}", insertText)
+ if data.jsxInitializer.initializer != nil {
+ replacementSpan = l.createLspRangeFromNode(data.jsxInitializer.initializer, file)
+ }
+ }
+
+ if originIsPromise(origin) && data.propertyAccessToConvert != nil {
+ if insertText == "" {
+ insertText = name
+ }
+ precedingToken := astnav.FindPrecedingToken(file, data.propertyAccessToConvert.Pos())
+ var awaitText string
+ if precedingToken != nil && positionIsASICandidate(precedingToken.End(), precedingToken.Parent, file) {
+ awaitText = ";"
+ }
+
+ awaitText += "(await " + scanner.GetTextOfNode(data.propertyAccessToConvert.Expression()) + ")"
+ if needsConvertPropertyAccess {
+ insertText = awaitText + insertText
+ } else {
+ dotStr := core.IfElse(insertQuestionDot, "?.", ".")
+ insertText = awaitText + dotStr + insertText
+ }
+ isInAwaitExpression := ast.IsAwaitExpression(data.propertyAccessToConvert.Parent)
+ wrapNode := core.IfElse(
+ isInAwaitExpression,
+ data.propertyAccessToConvert.Parent,
+ data.propertyAccessToConvert.Expression(),
+ )
+ replacementSpan = l.createLspRangeFromBounds(
+ astnav.GetStartOfNode(wrapNode, file, false /*includeJSDoc*/),
+ data.propertyAccessToConvert.End(),
+ file)
+ }
+
+ if originIsResolvedExport(origin) {
+ labelDetails = &lsproto.CompletionItemLabelDetails{
+ Description: &origin.asResolvedExport().moduleSpecifier, // !!! vscode @link support
+ }
+ if data.importStatementCompletion != nil {
+ // !!! auto-imports
+ }
+ }
+
+ if originIsTypeOnlyAlias(origin) {
+ hasAction = true
+ }
+
+ // Provide object member completions when missing commas, and insert missing commas.
+ // For example:
+ //
+ // interface I {
+ // a: string;
+ // b: number
+ // }
+ //
+ // const cc: I = { a: "red" | }
+ //
+ // Completion should add a comma after "red" and provide completions for b
+ if data.completionKind == CompletionKindObjectPropertyDeclaration &&
+ contextToken != nil &&
+ !ast.NodeHasKind(astnav.FindPrecedingTokenEx(file, contextToken.Pos(), contextToken, false /*excludeJSDoc*/), ast.KindCommaToken) {
+ if ast.IsMethodDeclaration(contextToken.Parent.Parent) ||
+ ast.IsGetAccessorDeclaration(contextToken.Parent.Parent) ||
+ ast.IsSetAccessorDeclaration(contextToken.Parent.Parent) ||
+ ast.IsSpreadAssignment(contextToken.Parent) ||
+ getLastToken(ast.FindAncestor(contextToken.Parent, ast.IsPropertyAssignment), file) == contextToken ||
+ ast.IsShorthandPropertyAssignment(contextToken.Parent) &&
+ getLineOfPosition(file, contextToken.End()) != getLineOfPosition(file, position) {
+ source = string(completionSourceObjectLiteralMemberWithComma)
+ hasAction = true
+ }
+ }
+
+ if ptrIsTrue(preferences.IncludeCompletionsWithClassMemberSnippets) &&
+ data.completionKind == CompletionKindMemberLike &&
+ isClassLikeMemberCompletion(symbol, data.location, file) {
+ // !!! class member completions
+ }
+
+ if originIsObjectLiteralMethod(origin) {
+ insertText = origin.asObjectLiteralMethod().insertText
+ isSnippet = origin.asObjectLiteralMethod().isSnippet
+ labelDetails = origin.asObjectLiteralMethod().labelDetails // !!! check if this can conflict with case above where we set label details
+ if !ptrIsTrue(clientOptions.CompletionItem.LabelDetailsSupport) {
+ name = name + *origin.asObjectLiteralMethod().labelDetails.Detail
+ labelDetails = nil
+ }
+ source = string(completionSourceObjectLiteralMethodSnippet)
+ sortText = sortBelow(sortText)
+ }
+
+ if data.isJsxIdentifierExpected &&
+ !data.isRightOfOpenTag &&
+ ptrIsTrue(clientOptions.CompletionItem.SnippetSupport) &&
+ !jsxAttributeCompletionStyleIs(preferences.JsxAttributeCompletionStyle, JsxAttributeCompletionStyleNone) &&
+ !(ast.IsJsxAttribute(data.location.Parent) && data.location.Parent.Initializer() != nil) {
+ useBraces := jsxAttributeCompletionStyleIs(preferences.JsxAttributeCompletionStyle, JsxAttributeCompletionStyleBraces)
+ t := typeChecker.GetTypeOfSymbolAtLocation(symbol, data.location)
+
+ // If is boolean like or undefined, don't return a snippet, we want to return just the completion.
+ if jsxAttributeCompletionStyleIs(preferences.JsxAttributeCompletionStyle, JsxAttributeCompletionStyleAuto) &&
+ !t.IsBooleanLike() &&
+ !(t.IsUnion() && core.Some(t.Types(), (*checker.Type).IsBooleanLike)) {
+ if t.IsStringLike() ||
+ t.IsUnion() &&
+ core.Every(
+ t.Types(),
+ func(t *checker.Type) bool {
+ return t.Flags()&(checker.TypeFlagsStringLike|checker.TypeFlagsUndefined) != 0 ||
+ isStringAndEmptyAnonymousObjectIntersection(typeChecker, t)
+ }) {
+ // If type is string-like or undefined, use quotes.
+ insertText = fmt.Sprintf("%s=%s", escapeSnippetText(name), quote(file, preferences, "$1"))
+ isSnippet = true
+ } else {
+ // Use braces for everything else.
+ useBraces = true
+ }
+ }
+
+ if useBraces {
+ insertText = escapeSnippetText(name) + "={$1}"
+ isSnippet = true
+ }
+ }
+
+ if originIsExport(origin) || originIsResolvedExport(origin) {
+ // !!! auto-imports
+ // data = originToCompletionEntryData(origin)
+ // hasAction = importStatementCompletion == nil
+ }
+
+ parentNamedImportOrExport := ast.FindAncestor(data.location, isNamedImportsOrExports)
+ if parentNamedImportOrExport != nil {
+ languageVersion := compilerOptions.GetEmitScriptTarget()
+ if !scanner.IsIdentifierText(name, languageVersion) {
+ insertText = quotePropertyName(file, preferences, name)
+
+ if parentNamedImportOrExport.Kind == ast.KindNamedImports {
+ // Check if it is `import { ^here as name } from '...'``.
+ // We have to access the scanner here to check if it is `{ ^here as name }`` or `{ ^here, as, name }`.
+ scanner := scanner.NewScanner()
+ scanner.SetText(file.Text())
+ scanner.ResetPos(position)
+ if !(scanner.Scan() == ast.KindAsKeyword && scanner.Scan() == ast.KindIdentifier) {
+ insertText += " as " + generateIdentifierForArbitraryString(name, languageVersion)
+ }
+ }
+ } else if parentNamedImportOrExport.Kind == ast.KindNamedImports {
+ possibleToken := scanner.StringToToken(name)
+ if possibleToken != ast.KindUnknown &&
+ (possibleToken == ast.KindAwaitKeyword || isNonContextualKeyword(possibleToken)) {
+ insertText = fmt.Sprintf("%s as %s_", name, name)
+ }
+ }
+ }
+
+ elementKind := getSymbolKind(typeChecker, symbol, data.location)
+ kind := getCompletionsSymbolKind(elementKind)
+ var commitCharacters *[]string
+ if ptrIsTrue(clientOptions.CompletionItem.CommitCharactersSupport) {
+ if elementKind == ScriptElementKindWarning || elementKind == ScriptElementKindString {
+ commitCharacters = &[]string{}
+ } else if !supportsDefaultCommitCharacters(clientOptions) {
+ commitCharacters = ptrTo(data.defaultCommitCharacters)
+ }
+ // Otherwise use the completion list default.
+ }
+
+ kindModifiers := getSymbolModifiers(typeChecker, symbol)
+ var tags *[]lsproto.CompletionItemTag
+ var detail *string
+ // Copied from vscode ts extension.
+ if kindModifiers.Has(ScriptElementKindModifierOptional) {
+ if insertText == "" {
+ insertText = name
+ }
+ if filterText == "" {
+ filterText = name
+ }
+ name = name + "?"
+ }
+ if kindModifiers.Has(ScriptElementKindModifierDeprecated) {
+ tags = &[]lsproto.CompletionItemTag{lsproto.CompletionItemTagDeprecated}
+ }
+ if kind == lsproto.CompletionItemKindFile {
+ for _, extensionModifier := range fileExtensionKindModifiers {
+ if kindModifiers.Has(extensionModifier) {
+ if strings.HasSuffix(name, string(extensionModifier)) {
+ detail = ptrTo(name)
+ } else {
+ detail = ptrTo(name + string(extensionModifier))
+ }
+ break
+ }
+ }
+ }
+
+ if hasAction && source != "" {
+ // !!! adjust label like vscode does
+ }
+
+ var insertTextFormat *lsproto.InsertTextFormat
+ if isSnippet {
+ insertTextFormat = ptrTo(lsproto.InsertTextFormatSnippet)
+ } else {
+ insertTextFormat = ptrTo(lsproto.InsertTextFormatPlainText)
+ }
+
+ var textEdit *lsproto.TextEditOrInsertReplaceEdit
+ if replacementSpan != nil {
+ textEdit = &lsproto.TextEditOrInsertReplaceEdit{
+ TextEdit: &lsproto.TextEdit{
+ NewText: core.IfElse(insertText == "", name, insertText),
+ Range: *replacementSpan,
+ },
+ }
+ }
+ // !!! adjust text edit like vscode does when Strada's `isMemberCompletion` is true
+
+ return &lsproto.CompletionItem{
+ Label: name,
+ LabelDetails: labelDetails,
+ Kind: &kind,
+ Tags: tags,
+ Detail: detail,
+ Preselect: boolToPtr(isRecommendedCompletionMatch(symbol, data.recommendedCompletion, typeChecker)),
+ SortText: ptrTo(string(sortText)),
+ FilterText: strPtrTo(filterText),
+ InsertText: strPtrTo(insertText),
+ InsertTextFormat: insertTextFormat,
+ TextEdit: textEdit,
+ CommitCharacters: commitCharacters,
+ Data: nil, // !!! auto-imports
+ }
+}
+
+func supportsDefaultCommitCharacters(clientOptions *lsproto.CompletionClientCapabilities) bool {
+ return clientOptions.CompletionList.ItemDefaults != nil &&
+ slices.Contains(*clientOptions.CompletionList.ItemDefaults, "commitCharacters")
+}
+
+func isRecommendedCompletionMatch(localSymbol *ast.Symbol, recommendedCompletion *ast.Symbol, typeChecker *checker.Checker) bool {
+ return localSymbol == recommendedCompletion ||
+ localSymbol.Flags&ast.SymbolFlagsExportValue != 0 && typeChecker.GetExportSymbolOfSymbol(localSymbol) == recommendedCompletion
+}
+
+func strPtrTo(v string) *string {
+ if v == "" {
+ return nil
+ }
+ return &v
+}
+
+func ptrIsTrue(ptr *bool) bool {
+ if ptr == nil {
+ return false
+ }
+ return *ptr
+}
+
+func ptrIsFalse(ptr *bool) bool {
+ if ptr == nil {
+ return false
+ }
+ return !*ptr
+}
+
+func boolToPtr(v bool) *bool {
+ if v {
+ return ptrTo(true)
+ }
+ return nil
+}
+
+func jsxAttributeCompletionStyleIs(preferenceStyle *JsxAttributeCompletionStyle, style JsxAttributeCompletionStyle) bool {
+ if preferenceStyle == nil {
+ return false
+ }
+ return *preferenceStyle == style
+}
+
+func getLineOfPosition(file *ast.SourceFile, pos int) int {
+ line, _ := scanner.GetLineAndCharacterOfPosition(file, pos)
+ return line
+}
+
+func isClassLikeMemberCompletion(symbol *ast.Symbol, location *ast.Node, file *ast.SourceFile) bool {
+ // !!! class member completions
+ return false
+}
+
+func symbolAppearsToBeTypeOnly(symbol *ast.Symbol, typeChecker *checker.Checker) bool {
+ flags := checker.GetCombinedLocalAndExportSymbolFlags(checker.SkipAlias(symbol, typeChecker))
+ return flags&ast.SymbolFlagsValue == 0 &&
+ (len(symbol.Declarations) == 0 || !ast.IsInJSFile(symbol.Declarations[0]) || flags&ast.SymbolFlagsType != 0)
+}
+
+func shouldIncludeSymbol(
+ symbol *ast.Symbol,
+ data *completionData,
+ closestSymbolDeclaration *ast.Declaration,
+ file *ast.SourceFile,
+ typeChecker *checker.Checker,
+ compilerOptions *core.CompilerOptions,
+) bool {
+ allFlags := symbol.Flags
+ location := data.location
+ if !ast.IsSourceFile(location) {
+ // export = /**/ here we want to get all meanings, so any symbol is ok
+ if ast.IsExportAssignment(location.Parent) {
+ return true
+ }
+
+ // Filter out variables from their own initializers
+ // `const a = /* no 'a' here */`
+ if closestSymbolDeclaration != nil &&
+ ast.IsVariableDeclaration(closestSymbolDeclaration) &&
+ symbol.ValueDeclaration == closestSymbolDeclaration {
+ return false
+ }
+
+ // Filter out current and latter parameters from defaults
+ // `function f(a = /* no 'a' and 'b' here */, b) { }` or
+ // `function f(a: T, b: T2) { }`
+ var symbolDeclaration *ast.Declaration
+ if symbol.ValueDeclaration != nil {
+ symbolDeclaration = symbol.ValueDeclaration
+ } else if len(symbol.Declarations) > 0 {
+ symbolDeclaration = symbol.Declarations[0]
+ }
+
+ if closestSymbolDeclaration != nil && symbolDeclaration != nil {
+ if ast.IsParameter(closestSymbolDeclaration) && ast.IsParameter(symbolDeclaration) {
+ parameters := closestSymbolDeclaration.Parent.ParameterList()
+ if symbolDeclaration.Pos() >= closestSymbolDeclaration.Pos() &&
+ symbolDeclaration.Pos() < parameters.End() {
+ return false
+ }
+ } else if ast.IsTypeParameterDeclaration(closestSymbolDeclaration) &&
+ ast.IsTypeParameterDeclaration(symbolDeclaration) {
+ if closestSymbolDeclaration == symbolDeclaration && data.contextToken != nil && data.contextToken.Kind == ast.KindExtendsKeyword {
+ // filter out the directly self-recursive type parameters
+ // `type A = K`
+ return false
+ }
+ if isInTypeParameterDefault(data.contextToken) && !ast.IsInferTypeNode(closestSymbolDeclaration.Parent) {
+ typeParameters := closestSymbolDeclaration.Parent.TypeParameterList()
+ if typeParameters != nil && symbolDeclaration.Pos() >= closestSymbolDeclaration.Pos() &&
+ symbolDeclaration.Pos() < typeParameters.End() {
+ return false
+ }
+ }
+ }
+ }
+
+ // External modules can have global export declarations that will be
+ // available as global keywords in all scopes. But if the external module
+ // already has an explicit export and user only wants to use explicit
+ // module imports then the global keywords will be filtered out so auto
+ // import suggestions will win in the completion.
+ symbolOrigin := checker.SkipAlias(symbol, typeChecker)
+ // We only want to filter out the global keywords.
+ // Auto Imports are not available for scripts so this conditional is always false.
+ if file.AsSourceFile().ExternalModuleIndicator != nil &&
+ compilerOptions.AllowUmdGlobalAccess != core.TSTrue &&
+ data.symbolToSortTextMap[ast.GetSymbolId(symbol)] == SortTextGlobalsOrKeywords &&
+ (data.symbolToSortTextMap[ast.GetSymbolId(symbolOrigin)] == SortTextAutoImportSuggestions ||
+ data.symbolToSortTextMap[ast.GetSymbolId(symbolOrigin)] == SortTextLocationPriority) {
+ return false
+ }
+
+ allFlags = allFlags | checker.GetCombinedLocalAndExportSymbolFlags(symbolOrigin)
+
+ // import m = /**/ <-- It can only access namespace (if typing import = x. this would get member symbols and not namespace)
+ if isInRightSideOfInternalImportEqualsDeclaration(data.location) {
+ return allFlags&ast.SymbolFlagsNamespace != 0
+ }
+
+ if data.isTypeOnlyLocation {
+ // It's a type, but you can reach it by namespace.type as well.
+ return symbolCanBeReferencedAtTypeLocation(symbol, typeChecker, core.Set[ast.SymbolId]{})
+ }
+ }
+
+ // expressions are value space (which includes the value namespaces)
+ return allFlags&ast.SymbolFlagsValue != 0
+}
+
+func getCompletionEntryDisplayNameForSymbol(
+ symbol *ast.Symbol,
+ target core.ScriptTarget,
+ origin *symbolOriginInfo,
+ completionKind CompletionKind,
+ isJsxIdentifierExpected bool,
+) (displayName string, needsConvertPropertyAccess bool) {
+ if originIsIgnore(origin) {
+ return "", false
+ }
+
+ var name string
+ if originIncludesSymbolName(origin) {
+ name = origin.symbolName()
+ } else {
+ name = symbol.Name
+ }
+ if name == "" ||
+ // If the symbol is external module, don't show it in the completion list
+ // (i.e declare module "http" { const x; } | // <= request completion here, "http" should not be there)
+ symbol.Flags&ast.SymbolFlagsModule != 0 && startsWithQuote(name) ||
+ // If the symbol is the internal name of an ES symbol, it is not a valid entry. Internal names for ES symbols start with "__@"
+ checker.IsKnownSymbol(symbol) {
+ return "", false
+ }
+
+ // !!! isIdentifierText should take in identifierVariant language variant
+ // name is a valid identifier or private identifier text
+ if scanner.IsIdentifierText(name, target) ||
+ symbol.ValueDeclaration != nil && ast.IsPrivateIdentifierClassElementDeclaration(symbol.ValueDeclaration) {
+ return name, false
+ }
+ if symbol.Flags&ast.SymbolFlagsAlias != 0 {
+ // Allow non-identifier import/export aliases since we can insert them as string literals
+ return name, true
+ }
+
+ switch completionKind {
+ case CompletionKindMemberLike:
+ if originIsComputedPropertyName(origin) {
+ return origin.symbolName(), false
+ }
+ return "", false
+ case CompletionKindObjectPropertyDeclaration:
+ // TODO: microsoft/TypeScript#18169
+ escapedName, _ := core.StringifyJson(name, "", "")
+ return escapedName, false
+ case CompletionKindPropertyAccess, CompletionKindGlobal:
+ // For a 'this.' completion it will be in a global context, but may have a non-identifier name.
+ // Don't add a completion for a name starting with a space. See https://github.com/Microsoft/TypeScript/pull/20547
+ ch, _ := utf8.DecodeRuneInString(name)
+ if ch == ' ' {
+ return "", false
+ }
+ return name, true
+ case CompletionKindNone, CompletionKindString:
+ return name, false
+ default:
+ panic(fmt.Sprintf("Unexpected completion kind: %v", completionKind))
+ }
+}
+
+// !!! refactor symbolOriginInfo so that we can tell the difference between flags and the kind of data it has
+func originIsIgnore(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindIgnore != 0
+}
+
+func originIncludesSymbolName(origin *symbolOriginInfo) bool {
+ return originIsExport(origin) || originIsResolvedExport(origin) || originIsComputedPropertyName(origin)
+}
+
+func originIsExport(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindExport != 0
+}
+
+func originIsResolvedExport(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindResolvedExport != 0
+}
+
+func originIsComputedPropertyName(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindComputedPropertyName != 0
+}
+
+func originIsObjectLiteralMethod(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindObjectLiteralMethod != 0
+}
+
+func originIsThisType(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindThisType != 0
+}
+
+func originIsTypeOnlyAlias(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindTypeOnlyAlias != 0
+}
+
+func originIsSymbolMember(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindSymbolMember != 0
+}
+
+func originIsNullableMember(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindNullable != 0
+}
+
+func originIsPromise(origin *symbolOriginInfo) bool {
+ return origin != nil && origin.kind&symbolOriginInfoKindPromise != 0
+}
+
+func getSourceFromOrigin(origin *symbolOriginInfo) string {
+ if originIsExport(origin) {
+ return core.StripQuotes(origin.asExport().moduleSymbol.Name)
+ }
+
+ if originIsResolvedExport(origin) {
+ return origin.asResolvedExport().moduleSpecifier
+ }
+
+ if originIsThisType(origin) {
+ return string(completionSourceThisProperty)
+ }
+
+ if originIsTypeOnlyAlias(origin) {
+ return string(completionSourceTypeOnlyAlias)
+ }
+
+ return ""
+}
+
+// In a scenarion such as `const x = 1 * |`, the context and previous tokens are both `*`.
+// In `const x = 1 * o|`, the context token is *, and the previous token is `o`.
+// `contextToken` and `previousToken` can both be nil if we are at the beginning of the file.
+func getRelevantTokens(position int, file *ast.SourceFile) (contextToken *ast.Node, previousToken *ast.Node) {
+ previousToken = astnav.FindPrecedingToken(file, position)
+ if previousToken != nil && position <= previousToken.End() && (ast.IsMemberName(previousToken) || ast.IsKeywordKind(previousToken.Kind)) {
+ contextToken := astnav.FindPrecedingToken(file, previousToken.Pos())
+ return contextToken, previousToken
+ }
+ return previousToken, previousToken
+}
+
+// "." | '"' | "'" | "`" | "/" | "@" | "<" | "#" | " "
+type CompletionsTriggerCharacter = string
+
+func isValidTrigger(file *ast.SourceFile, triggerCharacter CompletionsTriggerCharacter, contextToken *ast.Node, position int) bool {
+ switch triggerCharacter {
+ case ".", "@":
+ return true
+ case "\"", "'", "`":
+ // Only automatically bring up completions if this is an opening quote.
+ return contextToken != nil &&
+ isStringLiteralOrTemplate(contextToken) &&
+ position == astnav.GetStartOfNode(contextToken, file, false /*includeJSDoc*/)+1
+ case "#":
+ return contextToken != nil &&
+ ast.IsPrivateIdentifier(contextToken) &&
+ ast.GetContainingClass(contextToken) != nil
+ case "<":
+ // Opening JSX tag
+ return contextToken != nil &&
+ contextToken.Kind == ast.KindLessThanToken &&
+ (!ast.IsBinaryExpression(contextToken.Parent) || binaryExpressionMayBeOpenTag(contextToken.Parent.AsBinaryExpression()))
+ case "/":
+ if contextToken == nil {
+ return false
+ }
+ if ast.IsStringLiteralLike(contextToken) {
+ return tryGetImportFromModuleSpecifier(contextToken) != nil
+ }
+ return contextToken.Kind == ast.KindSlashToken && ast.IsJsxClosingElement(contextToken.Parent)
+ case " ":
+ return contextToken != nil && contextToken.Kind == ast.KindImportKeyword && contextToken.Parent.Kind == ast.KindSourceFile
+ default:
+ panic("Unknown trigger character: " + triggerCharacter)
+ }
+}
+
+func isStringLiteralOrTemplate(node *ast.Node) bool {
+ switch node.Kind {
+ case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral, ast.KindTemplateExpression,
+ ast.KindTaggedTemplateExpression:
+ return true
+ }
+ return false
+}
+
+func binaryExpressionMayBeOpenTag(binaryExpression *ast.BinaryExpression) bool {
+ return ast.NodeIsMissing(binaryExpression.Left)
+}
+
+func isCheckedFile(file *ast.SourceFile, compilerOptions *core.CompilerOptions) bool {
+ return !ast.IsSourceFileJS(file) || ast.IsCheckJSEnabledForFile(file, compilerOptions)
+}
+
+func isContextTokenValueLocation(contextToken *ast.Node) bool {
+ return contextToken != nil && ((contextToken.Kind == ast.KindTypeOfKeyword &&
+ (contextToken.Parent.Kind == ast.KindTypeQuery || ast.IsTypeOfExpression(contextToken.Parent))) ||
+ (contextToken.Kind == ast.KindAssertsKeyword && contextToken.Parent.Kind == ast.KindTypePredicate))
+}
+
+func isPossiblyTypeArgumentPosition(token *ast.Node, sourceFile *ast.SourceFile, typeChecker *checker.Checker) bool {
+ info := getPossibleTypeArgumentsInfo(token, sourceFile)
+ return info != nil && (ast.IsPartOfTypeNode(info.called) ||
+ len(getPossibleGenericSignatures(info.called, info.nTypeArguments, typeChecker)) != 0 ||
+ isPossiblyTypeArgumentPosition(info.called, sourceFile, typeChecker))
+}
+
+func isContextTokenTypeLocation(contextToken *ast.Node) bool {
+ if contextToken != nil {
+ parentKind := contextToken.Parent.Kind
+ switch contextToken.Kind {
+ case ast.KindColonToken:
+ return parentKind == ast.KindPropertyDeclaration ||
+ parentKind == ast.KindPropertySignature ||
+ parentKind == ast.KindParameter ||
+ parentKind == ast.KindVariableDeclaration ||
+ ast.IsFunctionLikeKind(parentKind)
+ case ast.KindEqualsToken:
+ return parentKind == ast.KindTypeAliasDeclaration || parentKind == ast.KindTypeParameter
+ case ast.KindAsKeyword:
+ return parentKind == ast.KindAsExpression
+ case ast.KindLessThanToken:
+ return parentKind == ast.KindTypeReference || parentKind == ast.KindTypeAssertionExpression
+ case ast.KindExtendsKeyword:
+ return parentKind == ast.KindTypeParameter
+ case ast.KindSatisfiesKeyword:
+ return parentKind == ast.KindSatisfiesExpression
+ }
+ }
+ return false
+}
+
+// True if symbol is a type or a module containing at least one type.
+func symbolCanBeReferencedAtTypeLocation(symbol *ast.Symbol, typeChecker *checker.Checker, seenModules core.Set[ast.SymbolId]) bool {
+ // Since an alias can be merged with a local declaration, we need to test both the alias and its target.
+ // This code used to just test the result of `skipAlias`, but that would ignore any locally introduced meanings.
+ return nonAliasCanBeReferencedAtTypeLocation(symbol, typeChecker, seenModules) ||
+ nonAliasCanBeReferencedAtTypeLocation(
+ checker.SkipAlias(core.IfElse(symbol.ExportSymbol != nil, symbol.ExportSymbol, symbol), typeChecker),
+ typeChecker,
+ seenModules,
+ )
+}
+
+func nonAliasCanBeReferencedAtTypeLocation(symbol *ast.Symbol, typeChecker *checker.Checker, seenModules core.Set[ast.SymbolId]) bool {
+ return symbol.Flags&ast.SymbolFlagsType != 0 || typeChecker.IsUnknownSymbol(symbol) ||
+ symbol.Flags&ast.SymbolFlagsModule != 0 && seenModules.AddIfAbsent(ast.GetSymbolId(symbol)) &&
+ core.Some(
+ typeChecker.GetExportsOfModule(symbol),
+ func(e *ast.Symbol) bool { return symbolCanBeReferencedAtTypeLocation(e, typeChecker, seenModules) })
+}
+
+// Gets all properties on a type, but if that type is a union of several types,
+// excludes array-like types or callable/constructable types.
+func getPropertiesForCompletion(t *checker.Type, typeChecker *checker.Checker) []*ast.Symbol {
+ if t.IsUnion() {
+ return core.CheckEachDefined(typeChecker.GetAllPossiblePropertiesOfTypes(t.Types()), "getAllPossiblePropertiesOfTypes() should all be defined.")
+ } else {
+ return core.CheckEachDefined(typeChecker.GetApparentProperties(t), "getApparentProperties() should all be defined.")
+ }
+}
+
+// Given 'a.b.c', returns 'a'.
+func getLeftMostName(e *ast.Expression) *ast.IdentifierNode {
+ if ast.IsIdentifier(e) {
+ return e
+ } else if ast.IsPropertyAccessExpression(e) {
+ return getLeftMostName(e.Expression())
+ } else {
+ return nil
+ }
+}
+
+func getFirstSymbolInChain(symbol *ast.Symbol, enclosingDeclaration *ast.Node, typeChecker *checker.Checker) *ast.Symbol {
+ chain := typeChecker.GetAccessibleSymbolChain(
+ symbol,
+ enclosingDeclaration,
+ ast.SymbolFlagsAll, /*meaning*/
+ false /*useOnlyExternalAliasing*/)
+ if len(chain) > 0 {
+ return chain[0]
+ }
+ if symbol.Parent != nil {
+ if isModuleSymbol(symbol.Parent) {
+ return symbol
+ }
+ return getFirstSymbolInChain(symbol.Parent, enclosingDeclaration, typeChecker)
+ }
+ return nil
+}
+
+func isModuleSymbol(symbol *ast.Symbol) bool {
+ return core.Some(symbol.Declarations, func(decl *ast.Declaration) bool { return decl.Kind == ast.KindSourceFile })
+}
+
+func getNullableSymbolOriginInfoKind(kind symbolOriginInfoKind, insertQuestionDot bool) symbolOriginInfoKind {
+ if insertQuestionDot {
+ kind |= symbolOriginInfoKindNullable
+ }
+ return kind
+}
+
+func isStaticProperty(symbol *ast.Symbol) bool {
+ return symbol.ValueDeclaration != nil &&
+ symbol.ValueDeclaration.ModifierFlags()&ast.ModifierFlagsStatic != 0 &&
+ ast.IsClassLike(symbol.ValueDeclaration.Parent)
+}
+
+func getContextualType(previousToken *ast.Node, position int, file *ast.SourceFile, typeChecker *checker.Checker) *checker.Type {
+ parent := previousToken.Parent
+ switch previousToken.Kind {
+ case ast.KindIdentifier:
+ return getContextualTypeFromParent(previousToken, typeChecker, checker.ContextFlagsNone)
+ case ast.KindEqualsToken:
+ switch parent.Kind {
+ case ast.KindVariableDeclaration:
+ return typeChecker.GetContextualType(parent.Initializer(), checker.ContextFlagsNone)
+ case ast.KindBinaryExpression:
+ return typeChecker.GetTypeAtLocation(parent.AsBinaryExpression().Left)
+ case ast.KindJsxAttribute:
+ // return typeChecker.GetContextualTypeForJsxAttribute(parent) // !!! jsx
+ return nil
+ default:
+ return nil
+ }
+ case ast.KindNewKeyword:
+ return typeChecker.GetContextualType(parent, checker.ContextFlagsNone)
+ case ast.KindCaseKeyword:
+ caseClause := core.IfElse(ast.IsCaseClause(parent), parent, nil)
+ if caseClause != nil {
+ return getSwitchedType(caseClause, typeChecker)
+ }
+ return nil
+ case ast.KindOpenBraceToken:
+ if ast.IsJsxExpression(parent) && !ast.IsJsxElement(parent.Parent) && !ast.IsJsxFragment(parent.Parent) {
+ // return typeChecker.GetContextualTypeForJsxAttribute(parent.Parent) // !!! jsx
+ return nil
+ }
+ return nil
+ default:
+ // argInfo := getArgumentInfoForCompletions(previousToken, position, file, typeChecker) // !!! signature help
+ var argInfo *struct{} // !!! signature help
+ if argInfo != nil {
+ // !!! signature help
+ return nil
+ } else if isEqualityOperatorKind(previousToken.Kind) && ast.IsBinaryExpression(parent) && isEqualityOperatorKind(parent.AsBinaryExpression().OperatorToken.Kind) {
+ // completion at `x ===/**/`
+ return typeChecker.GetTypeAtLocation(parent.AsBinaryExpression().Left)
+ } else {
+ contextualType := typeChecker.GetContextualType(previousToken, checker.ContextFlagsCompletions)
+ if contextualType != nil {
+ return contextualType
+ }
+ return typeChecker.GetContextualType(previousToken, checker.ContextFlagsNone)
+ }
+ }
+}
+
+func getContextualTypeFromParent(node *ast.Expression, typeChecker *checker.Checker, contextFlags checker.ContextFlags) *checker.Type {
+ parent := ast.WalkUpParenthesizedExpressions(node.Parent)
+ switch parent.Kind {
+ case ast.KindNewExpression:
+ return typeChecker.GetContextualType(parent, contextFlags)
+ case ast.KindBinaryExpression:
+ if isEqualityOperatorKind(parent.AsBinaryExpression().OperatorToken.Kind) {
+ return typeChecker.GetTypeAtLocation(
+ core.IfElse(node == parent.AsBinaryExpression().Right, parent.AsBinaryExpression().Left, parent.AsBinaryExpression().Right))
+ }
+ return typeChecker.GetContextualType(node, contextFlags)
+ case ast.KindCaseClause:
+ return getSwitchedType(parent, typeChecker)
+ default:
+ return typeChecker.GetContextualType(node, contextFlags)
+ }
+}
+
+func getSwitchedType(caseClause *ast.CaseClauseNode, typeChecker *checker.Checker) *checker.Type {
+ return typeChecker.GetTypeAtLocation(caseClause.Parent.Parent.Expression())
+}
+
+func isEqualityOperatorKind(kind ast.Kind) bool {
+ switch kind {
+ case ast.KindEqualsEqualsEqualsToken, ast.KindEqualsEqualsToken,
+ ast.KindExclamationEqualsEqualsToken, ast.KindExclamationEqualsToken:
+ return true
+ default:
+ return false
+ }
+}
+
+func isLiteral(t *checker.Type) bool {
+ return t.IsStringLiteral() || t.IsNumberLiteral() || t.IsBigIntLiteral()
+}
+
+func getRecommendedCompletion(previousToken *ast.Node, contextualType *checker.Type, typeChecker *checker.Checker) *ast.Symbol {
+ var types []*checker.Type
+ if contextualType.IsUnion() {
+ types = contextualType.Types()
+ } else {
+ types = []*checker.Type{contextualType}
+ }
+ // For a union, return the first one with a recommended completion.
+ return core.FirstNonNil(
+ types,
+ func(t *checker.Type) *ast.Symbol {
+ symbol := t.Symbol()
+ // Don't make a recommended completion for an abstract class.
+ if symbol != nil &&
+ symbol.Flags&(ast.SymbolFlagsEnumMember|ast.SymbolFlagsEnum|ast.SymbolFlagsClass) != 0 &&
+ !isAbstractConstructorSymbol(symbol) {
+ return getFirstSymbolInChain(symbol, previousToken, typeChecker)
+ }
+ return nil
+ },
+ )
+}
+
+func isAbstractConstructorSymbol(symbol *ast.Symbol) bool {
+ if symbol.Flags&ast.SymbolFlagsClass != 0 {
+ declaration := ast.GetClassLikeDeclarationOfSymbol(symbol)
+ return declaration != nil && ast.HasSyntacticModifier(declaration, ast.ModifierFlagsAbstract)
+ }
+ return false
+}
+
+func startsWithQuote(s string) bool {
+ r, _ := utf8.DecodeRuneInString(s)
+ return r == '"' || r == '\''
+}
+
+func getClosestSymbolDeclaration(contextToken *ast.Node, location *ast.Node) *ast.Declaration {
+ if contextToken == nil {
+ return nil
+ }
+
+ closestDeclaration := ast.FindAncestorOrQuit(contextToken, func(node *ast.Node) ast.FindAncestorResult {
+ if ast.IsFunctionBlock(node) || isArrowFunctionBody(node) || ast.IsBindingPattern(node) {
+ return ast.FindAncestorQuit
+ }
+
+ if (ast.IsParameter(node) || ast.IsTypeParameterDeclaration(node)) &&
+ !ast.IsIndexSignatureDeclaration(node.Parent) {
+ return ast.FindAncestorTrue
+ }
+ return ast.FindAncestorFalse
+ })
+
+ if closestDeclaration == nil {
+ closestDeclaration = ast.FindAncestorOrQuit(contextToken, func(node *ast.Node) ast.FindAncestorResult {
+ if ast.IsFunctionBlock(node) || isArrowFunctionBody(node) || ast.IsBindingPattern(node) {
+ return ast.FindAncestorQuit
+ }
+
+ if ast.IsVariableDeclaration(node) {
+ return ast.FindAncestorTrue
+ }
+ return ast.FindAncestorFalse
+ })
+ }
+ return closestDeclaration
+}
+
+func isArrowFunctionBody(node *ast.Node) bool {
+ return node.Parent != nil && ast.IsArrowFunction(node.Parent) &&
+ (node.Parent.Body() == node ||
+ // const a = () => /**/;
+ node.Kind == ast.KindEqualsGreaterThanToken)
+}
+
+func isInTypeParameterDefault(contextToken *ast.Node) bool {
+ if contextToken == nil {
+ return false
+ }
+
+ node := contextToken
+ parent := contextToken.Parent
+ for parent != nil {
+ if ast.IsTypeParameterDeclaration(parent) {
+ return parent.AsTypeParameter().DefaultType == node || node.Kind == ast.KindEqualsToken
+ }
+ node = parent
+ parent = parent.Parent
+ }
+
+ return false
+}
+
+func isDeprecated(symbol *ast.Symbol, typeChecker *checker.Checker) bool {
+ declarations := checker.SkipAlias(symbol, typeChecker).Declarations
+ return len(declarations) > 0 && core.Every(declarations, func(decl *ast.Declaration) bool { return typeChecker.IsDeprecatedDeclaration(decl) })
+}
+
+func (l *LanguageService) getReplacementRangeForContextToken(file *ast.SourceFile, contextToken *ast.Node, position int) *lsproto.Range {
+ if contextToken == nil {
+ return nil
+ }
+
+ // !!! ensure range is single line
+ switch contextToken.Kind {
+ case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral:
+ return l.createRangeFromStringLiteralLikeContent(file, contextToken, position)
+ default:
+ return l.createLspRangeFromNode(contextToken, file)
+ }
+}
+
+func (l *LanguageService) createRangeFromStringLiteralLikeContent(file *ast.SourceFile, node *ast.StringLiteralLike, position int) *lsproto.Range {
+ replacementEnd := node.End() - 1
+ nodeStart := astnav.GetStartOfNode(node, file, false /*includeJSDoc*/)
+ if ast.IsUnterminatedLiteral(node) {
+ // we return no replacement range only if unterminated string is empty
+ if nodeStart == replacementEnd {
+ return nil
+ }
+ replacementEnd = min(position, node.End())
+ }
+ return l.createLspRangeFromBounds(nodeStart+1, replacementEnd, file)
+}
+
+func quotePropertyName(file *ast.SourceFile, preferences *UserPreferences, name string) string {
+ r, _ := utf8.DecodeRuneInString(name)
+ if unicode.IsDigit(r) {
+ return name
+ }
+ return quote(file, preferences, name)
+}
+
+// Checks whether type is `string & {}`, which is semantically equivalent to string but
+// is not reduced by the checker as a special case used for supporting string literal completions
+// for string type.
+func isStringAndEmptyAnonymousObjectIntersection(typeChecker *checker.Checker, t *checker.Type) bool {
+ if !t.IsIntersection() {
+ return false
+ }
+
+ return len(t.Types()) == 2 &&
+ (areIntersectedTypesAvoidingStringReduction(typeChecker, t.Types()[0], t.Types()[1]) ||
+ areIntersectedTypesAvoidingStringReduction(typeChecker, t.Types()[1], t.Types()[0]))
+}
+
+func areIntersectedTypesAvoidingStringReduction(typeChecker *checker.Checker, t1 *checker.Type, t2 *checker.Type) bool {
+ return t1.IsString() && typeChecker.IsEmptyAnonymousObjectType(t2)
+}
+
+func escapeSnippetText(text string) string {
+ return strings.ReplaceAll(text, `$`, `\$`)
+}
+
+func isNamedImportsOrExports(node *ast.Node) bool {
+ return ast.IsNamedImports(node) || ast.IsNamedExports(node)
+}
+
+func generateIdentifierForArbitraryString(text string, languageVersion core.ScriptTarget) string {
+ needsUnderscore := false
+ identifier := ""
+ var ch rune
+ var size int
+
+ // Convert "(example, text)" into "_example_text_"
+ for pos := 0; pos < len(text); pos += size {
+ ch, size = utf8.DecodeRuneInString(text[pos:])
+ var validChar bool
+ if pos == 0 {
+ validChar = scanner.IsIdentifierStart(ch, languageVersion)
+ } else {
+ validChar = scanner.IsIdentifierPart(ch, languageVersion)
+ }
+ if size > 0 && validChar {
+ if needsUnderscore {
+ identifier += "_"
+ identifier += string(ch)
+ needsUnderscore = false
+ }
+ } else {
+ needsUnderscore = true
+ }
+ }
+
+ if needsUnderscore {
+ identifier += "_"
+ }
+
+ // Default to "_" if the provided text was empty
+ if identifier == "" {
+ return "_"
+ }
+
+ return identifier
+}
+
+// Copied from vscode TS extension.
+func getCompletionsSymbolKind(kind ScriptElementKind) lsproto.CompletionItemKind {
+ switch kind {
+ case ScriptElementKindPrimitiveType, ScriptElementKindKeyword:
+ return lsproto.CompletionItemKindKeyword
+ case ScriptElementKindConstElement, ScriptElementKindLetElement, ScriptElementKindVariableElement,
+ ScriptElementKindLocalVariableElement, ScriptElementKindAlias, ScriptElementKindParameterElement:
+ return lsproto.CompletionItemKindVariable
+
+ case ScriptElementKindMemberVariableElement, ScriptElementKindMemberGetAccessorElement,
+ ScriptElementKindMemberSetAccessorElement:
+ return lsproto.CompletionItemKindField
+
+ case ScriptElementKindFunctionElement, ScriptElementKindLocalFunctionElement:
+ return lsproto.CompletionItemKindFunction
+
+ case ScriptElementKindMemberFunctionElement, ScriptElementKindConstructSignatureElement,
+ ScriptElementKindCallSignatureElement, ScriptElementKindIndexSignatureElement:
+ return lsproto.CompletionItemKindMethod
+
+ case ScriptElementKindEnumElement:
+ return lsproto.CompletionItemKindEnum
+
+ case ScriptElementKindEnumMemberElement:
+ return lsproto.CompletionItemKindEnumMember
+
+ case ScriptElementKindModuleElement, ScriptElementKindExternalModuleName:
+ return lsproto.CompletionItemKindModule
+
+ case ScriptElementKindClassElement, ScriptElementKindTypeElement:
+ return lsproto.CompletionItemKindClass
+
+ case ScriptElementKindInterfaceElement:
+ return lsproto.CompletionItemKindInterface
+
+ case ScriptElementKindWarning:
+ return lsproto.CompletionItemKindText
+
+ case ScriptElementKindScriptElement:
+ return lsproto.CompletionItemKindFile
+
+ case ScriptElementKindDirectory:
+ return lsproto.CompletionItemKindFolder
+
+ case ScriptElementKindString:
+ return lsproto.CompletionItemKindConstant
+
+ default:
+ return lsproto.CompletionItemKindProperty
+ }
+}
+
+// Editors will use the `sortText` and then fall back to `name` for sorting, but leave ties in response order.
+// So, it's important that we sort those ties in the order we want them displayed if it matters. We don't
+// strictly need to sort by name or SortText here since clients are going to do it anyway, but we have to
+// do the work of comparing them so we can sort those ties appropriately; plus, it makes the order returned
+// by the language service consistent with what TS Server does and what editors typically do. This also makes
+// completions tests make more sense. We used to sort only alphabetically and only in the server layer, but
+// this made tests really weird, since most fourslash tests don't use the server.
+func compareCompletionEntries(entryInSlice *lsproto.CompletionItem, entryToInsert *lsproto.CompletionItem) int {
+ // !!! use locale-aware comparison
+ result := stringutil.CompareStringsCaseSensitive(*entryInSlice.SortText, *entryToInsert.SortText)
+ if result == stringutil.ComparisonEqual {
+ result = stringutil.CompareStringsCaseSensitive(entryInSlice.Label, entryToInsert.Label)
+ }
+ // !!! auto-imports
+ // if (result === Comparison.EqualTo && entryInArray.data?.moduleSpecifier && entryToInsert.data?.moduleSpecifier) {
+ // // Sort same-named auto-imports by module specifier
+ // result = compareNumberOfDirectorySeparators(
+ // (entryInArray.data as CompletionEntryDataResolved).moduleSpecifier,
+ // (entryToInsert.data as CompletionEntryDataResolved).moduleSpecifier,
+ // );
+ // }
+ if result == stringutil.ComparisonEqual {
+ // Fall back to symbol order - if we return `EqualTo`, `insertSorted` will put later symbols first.
+ return stringutil.ComparisonLessThan
+ }
+
+ return result
+}
+
+var (
+ keywordCompletionsCache = collections.SyncMap[KeywordCompletionFilters, []*lsproto.CompletionItem]{}
+ allKeywordCompletions = sync.OnceValue(func() []*lsproto.CompletionItem {
+ result := make([]*lsproto.CompletionItem, 0, ast.KindLastKeyword-ast.KindFirstKeyword+1)
+ for i := ast.KindFirstKeyword; i <= ast.KindLastKeyword; i++ {
+ result = append(result, &lsproto.CompletionItem{
+ Label: scanner.TokenToString(i),
+ Kind: ptrTo(lsproto.CompletionItemKindKeyword),
+ SortText: ptrTo(string(SortTextGlobalsOrKeywords)),
+ })
+ }
+ return result
+ })
+)
+
+func getKeywordCompletions(keywordFilter KeywordCompletionFilters, filterOutTsOnlyKeywords bool) []*lsproto.CompletionItem {
+ if !filterOutTsOnlyKeywords {
+ return getTypescriptKeywordCompletions(keywordFilter)
+ }
+
+ index := keywordFilter + KeywordCompletionFiltersLast + 1
+ if cached, ok := keywordCompletionsCache.Load(index); ok {
+ return cached
+ }
+ result := core.Filter(
+ getTypescriptKeywordCompletions(keywordFilter),
+ func(ci *lsproto.CompletionItem) bool {
+ return !isTypeScriptOnlyKeyword(scanner.StringToToken(ci.Label))
+ })
+ keywordCompletionsCache.Store(index, result)
+ return result
+}
+
+func getTypescriptKeywordCompletions(keywordFilter KeywordCompletionFilters) []*lsproto.CompletionItem {
+ if cached, ok := keywordCompletionsCache.Load(keywordFilter); ok {
+ return cached
+ }
+ result := core.Filter(allKeywordCompletions(), func(entry *lsproto.CompletionItem) bool {
+ kind := scanner.StringToToken(entry.Label)
+ switch keywordFilter {
+ case KeywordCompletionFiltersNone:
+ return false
+ case KeywordCompletionFiltersAll:
+ return isFunctionLikeBodyKeyword(kind) ||
+ kind == ast.KindDeclareKeyword ||
+ kind == ast.KindModuleKeyword ||
+ kind == ast.KindTypeKeyword ||
+ kind == ast.KindNamespaceKeyword ||
+ kind == ast.KindAbstractKeyword ||
+ isTypeKeyword(kind) && kind != ast.KindUndefinedKeyword
+ case KeywordCompletionFiltersFunctionLikeBodyKeywords:
+ return isFunctionLikeBodyKeyword(kind)
+ case KeywordCompletionFiltersClassElementKeywords:
+ return isClassMemberCompletionKeyword(kind)
+ case KeywordCompletionFiltersInterfaceElementKeywords:
+ return isInterfaceOrTypeLiteralCompletionKeyword(kind)
+ case KeywordCompletionFiltersConstructorParameterKeywords:
+ return ast.IsParameterPropertyModifier(kind)
+ case KeywordCompletionFiltersTypeAssertionKeywords:
+ return isTypeKeyword(kind) || kind == ast.KindConstKeyword
+ case KeywordCompletionFiltersTypeKeywords:
+ return isTypeKeyword(kind)
+ case KeywordCompletionFiltersTypeKeyword:
+ return kind == ast.KindTypeKeyword
+ default:
+ panic(fmt.Sprintf("Unknown keyword filter: %v", keywordFilter))
+ }
+ })
+
+ keywordCompletionsCache.Store(keywordFilter, result)
+ return result
+}
+
+func isTypeScriptOnlyKeyword(kind ast.Kind) bool {
+ return false // !!! here
+}
+
+func isFunctionLikeBodyKeyword(kind ast.Kind) bool {
+ return kind == ast.KindAsyncKeyword ||
+ kind == ast.KindAwaitKeyword ||
+ kind == ast.KindUsingKeyword ||
+ kind == ast.KindAsKeyword ||
+ kind == ast.KindAssertsKeyword ||
+ kind == ast.KindTypeKeyword ||
+ !ast.IsContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind)
+}
+
+func isClassMemberCompletionKeyword(kind ast.Kind) bool {
+ switch kind {
+ case ast.KindAbstractKeyword, ast.KindAccessorKeyword, ast.KindConstructorKeyword, ast.KindGetKeyword,
+ ast.KindSetKeyword, ast.KindAsyncKeyword, ast.KindDeclareKeyword, ast.KindOverrideKeyword:
+ return true
+ default:
+ return ast.IsClassMemberModifier(kind)
+ }
+}
+
+func isInterfaceOrTypeLiteralCompletionKeyword(kind ast.Kind) bool {
+ return kind == ast.KindReadonlyKeyword
+}
+
+func isContextualKeywordInAutoImportableExpressionSpace(keyword string) bool {
+ return keyword == "abstract" ||
+ keyword == "async" ||
+ keyword == "await" ||
+ keyword == "declare" ||
+ keyword == "module" ||
+ keyword == "namespace" ||
+ keyword == "type" ||
+ keyword == "satisfies" ||
+ keyword == "as"
+}
+
+func getContextualKeywords(file *ast.SourceFile, contextToken *ast.Node, position int) []*lsproto.CompletionItem {
+ var entries []*lsproto.CompletionItem
+ // An `AssertClause` can come after an import declaration:
+ // import * from "foo" |
+ // import "foo" |
+ // or after a re-export declaration that has a module specifier:
+ // export { foo } from "foo" |
+ // Source: https://tc39.es/proposal-import-assertions/
+ if contextToken != nil {
+ parent := contextToken.Parent
+ tokenLine, _ := scanner.GetLineAndCharacterOfPosition(file, contextToken.End())
+ currentLine, _ := scanner.GetLineAndCharacterOfPosition(file, position)
+ if (ast.IsImportDeclaration(parent) ||
+ ast.IsExportDeclaration(parent) && parent.AsExportDeclaration().ModuleSpecifier != nil) &&
+ contextToken == parent.ModuleSpecifier() &&
+ tokenLine == currentLine {
+ entries = append(entries, &lsproto.CompletionItem{
+ Label: scanner.TokenToString(ast.KindAssertKeyword),
+ Kind: ptrTo(lsproto.CompletionItemKindKeyword),
+ SortText: ptrTo(string(SortTextGlobalsOrKeywords)),
+ })
+ }
+ }
+ return entries
+}
+
+func getJSCompletionEntries(
+ file *ast.SourceFile,
+ position int,
+ uniqueNames *core.Set[string],
+ target core.ScriptTarget,
+ sortedEntries []*lsproto.CompletionItem,
+) []*lsproto.CompletionItem {
+ nameTable := getNameTable(file)
+ for name, pos := range nameTable {
+ // Skip identifiers produced only from the current location
+ if pos == position {
+ continue
+ }
+ if !uniqueNames.Has(name) && scanner.IsIdentifierText(name, target) {
+ uniqueNames.Add(name)
+ sortedEntries = core.InsertSorted(
+ sortedEntries,
+ &lsproto.CompletionItem{
+ Label: name,
+ Kind: ptrTo(lsproto.CompletionItemKindText),
+ SortText: ptrTo(string(SortTextJavascriptIdentifiers)),
+ CommitCharacters: ptrTo([]string{}),
+ },
+ compareCompletionEntries,
+ )
+ }
+ }
+ return sortedEntries
+}
diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go
new file mode 100644
index 0000000000..883844835f
--- /dev/null
+++ b/internal/ls/completions_test.go
@@ -0,0 +1,130 @@
+package ls_test
+
+import (
+ "testing"
+
+ "github.com/microsoft/typescript-go/internal/bundled"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/ls"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
+ "gotest.tools/v3/assert"
+)
+
+var defaultCommitCharacters = []string{".", ",", ";"}
+
+type testCase struct {
+ name string
+ content string
+ position int
+ expected *lsproto.CompletionList
+}
+
+func TestCompletions(t *testing.T) {
+ t.Parallel()
+ if !bundled.Embedded {
+ // Without embedding, we'd need to read all of the lib files out from disk into the MapFS.
+ // Just skip this for now.
+ t.Skip("bundled files are not embedded")
+ }
+ testCases := []testCase{
+ {
+ name: "basicInterfaceMembers",
+ content: `export {};
+interface Point {
+ x: number;
+ y: number;
+}
+declare const p: Point;
+p.`,
+ position: 87,
+ expected: &lsproto.CompletionList{
+ IsIncomplete: false,
+ ItemDefaults: &lsproto.CompletionItemDefaults{
+ CommitCharacters: &defaultCommitCharacters,
+ },
+ Items: []*lsproto.CompletionItem{
+ {
+ Label: "x",
+ Kind: ptrTo(lsproto.CompletionItemKindField),
+ SortText: ptrTo(string(ls.SortTextLocationPriority)),
+ InsertTextFormat: ptrTo(lsproto.InsertTextFormatPlainText),
+ },
+ {
+ Label: "y",
+ Kind: ptrTo(lsproto.CompletionItemKindField),
+ SortText: ptrTo(string(ls.SortTextLocationPriority)),
+ InsertTextFormat: ptrTo(lsproto.InsertTextFormatPlainText),
+ },
+ },
+ },
+ },
+ {
+ name: "objectLiteralType",
+ content: `export {};
+let x = { foo: 123 };
+x.`,
+ position: 35,
+ expected: &lsproto.CompletionList{
+ IsIncomplete: false,
+ ItemDefaults: &lsproto.CompletionItemDefaults{
+ CommitCharacters: &defaultCommitCharacters,
+ },
+ Items: []*lsproto.CompletionItem{
+ {
+ Label: "foo",
+ Kind: ptrTo(lsproto.CompletionItemKindField),
+ SortText: ptrTo(string(ls.SortTextLocationPriority)),
+ InsertTextFormat: ptrTo(lsproto.InsertTextFormatPlainText),
+ },
+ },
+ },
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ t.Parallel()
+ runTest(t, testCase.content, testCase.position, testCase.expected)
+ })
+ }
+}
+
+func runTest(t *testing.T, content string, position int, expected *lsproto.CompletionList) {
+ files := map[string]string{
+ "/index.ts": content,
+ }
+ languageService := createLanguageService("/index.ts", files)
+ context := &lsproto.CompletionContext{
+ TriggerKind: lsproto.CompletionTriggerKindInvoked,
+ }
+ capabilities := &lsproto.CompletionClientCapabilities{
+ CompletionItem: &lsproto.ClientCompletionItemOptions{
+ SnippetSupport: ptrTo(true),
+ CommitCharactersSupport: ptrTo(true),
+ PreselectSupport: ptrTo(true),
+ LabelDetailsSupport: ptrTo(true),
+ },
+ CompletionList: &lsproto.CompletionListCapabilities{
+ ItemDefaults: &[]string{"commitCharacters"},
+ },
+ }
+ preferences := &ls.UserPreferences{}
+ completionList := languageService.ProvideCompletion(
+ "/index.ts",
+ position,
+ context,
+ capabilities,
+ preferences)
+ assert.DeepEqual(t, completionList, expected)
+}
+
+func createLanguageService(fileName string, files map[string]string) *ls.LanguageService {
+ projectService, _ := projecttestutil.Setup(files)
+ projectService.OpenFile(fileName, files[fileName], core.ScriptKindTS, "")
+ project := projectService.Projects()[0]
+ return project.LanguageService()
+}
+
+func ptrTo[T any](v T) *T {
+ return &v
+}
diff --git a/internal/ls/symbol_display.go b/internal/ls/symbol_display.go
new file mode 100644
index 0000000000..777eea8614
--- /dev/null
+++ b/internal/ls/symbol_display.go
@@ -0,0 +1,385 @@
+package ls
+
+import (
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/core"
+)
+
+type ScriptElementKind string
+
+const (
+ ScriptElementKindUnknown ScriptElementKind = ""
+ ScriptElementKindWarning ScriptElementKind = "warning"
+ // predefined type (void) or keyword (class)
+ ScriptElementKindKeyword ScriptElementKind = "keyword"
+ // top level script node
+ ScriptElementKindScriptElement ScriptElementKind = "script"
+ // module foo {}
+ ScriptElementKindModuleElement ScriptElementKind = "module"
+ // class X {}
+ ScriptElementKindClassElement ScriptElementKind = "class"
+ // var x = class X {}
+ ScriptElementKindLocalClassElement ScriptElementKind = "local class"
+ // interface Y {}
+ ScriptElementKindInterfaceElement ScriptElementKind = "interface"
+ // type T = ...
+ ScriptElementKindTypeElement ScriptElementKind = "type"
+ // enum E {}
+ ScriptElementKindEnumElement ScriptElementKind = "enum"
+ ScriptElementKindEnumMemberElement ScriptElementKind = "enum member"
+ // Inside module and script only.
+ // const v = ...
+ ScriptElementKindVariableElement ScriptElementKind = "var"
+ // Inside function.
+ ScriptElementKindLocalVariableElement ScriptElementKind = "local var"
+ // using foo = ...
+ ScriptElementKindVariableUsingElement ScriptElementKind = "using"
+ // await using foo = ...
+ ScriptElementKindVariableAwaitUsingElement ScriptElementKind = "await using"
+ // Inside module and script only.
+ // function f() {}
+ ScriptElementKindFunctionElement ScriptElementKind = "function"
+ // Inside function.
+ ScriptElementKindLocalFunctionElement ScriptElementKind = "local function"
+ // class X { [public|private]* foo() {} }
+ ScriptElementKindMemberFunctionElement ScriptElementKind = "method"
+ // class X { [public|private]* [get|set] foo:number; }
+ ScriptElementKindMemberGetAccessorElement ScriptElementKind = "getter"
+ ScriptElementKindMemberSetAccessorElement ScriptElementKind = "setter"
+ // class X { [public|private]* foo:number; }
+ // interface Y { foo:number; }
+ ScriptElementKindMemberVariableElement ScriptElementKind = "property"
+ // class X { [public|private]* accessor foo: number; }
+ ScriptElementKindMemberAccessorVariableElement ScriptElementKind = "accessor"
+ // class X { constructor() { } }
+ // class X { static { } }
+ ScriptElementKindConstructorImplementationElement ScriptElementKind = "constructor"
+ // interface Y { ():number; }
+ ScriptElementKindCallSignatureElement ScriptElementKind = "call"
+ // interface Y { []:number; }
+ ScriptElementKindIndexSignatureElement ScriptElementKind = "index"
+ // interface Y { new():Y; }
+ ScriptElementKindConstructSignatureElement ScriptElementKind = "construct"
+ // function foo(*Y*: string)
+ ScriptElementKindParameterElement ScriptElementKind = "parameter"
+ ScriptElementKindTypeParameterElement ScriptElementKind = "type parameter"
+ ScriptElementKindPrimitiveType ScriptElementKind = "primitive type"
+ ScriptElementKindLabel ScriptElementKind = "label"
+ ScriptElementKindAlias ScriptElementKind = "alias"
+ ScriptElementKindConstElement ScriptElementKind = "const"
+ ScriptElementKindLetElement ScriptElementKind = "let"
+ ScriptElementKindDirectory ScriptElementKind = "directory"
+ ScriptElementKindExternalModuleName ScriptElementKind = "external module name"
+ // String literal
+ ScriptElementKindString ScriptElementKind = "string"
+ // Jsdoc @link: in `{@link C link text}`, the before and after text "{@link " and "}"
+ ScriptElementKindLink ScriptElementKind = "link"
+ // Jsdoc @link: in `{@link C link text}`, the entity name "C"
+ ScriptElementKindLinkName ScriptElementKind = "link name"
+ // Jsdoc @link: in `{@link C link text}`, the link text "link text"
+ ScriptElementKindLinkText ScriptElementKind = "link text"
+)
+
+type ScriptElementKindModifier string
+
+const (
+ ScriptElementKindModifierNone ScriptElementKindModifier = ""
+ ScriptElementKindModifierPublic ScriptElementKindModifier = "public"
+ ScriptElementKindModifierPrivate ScriptElementKindModifier = "private"
+ ScriptElementKindModifierProtected ScriptElementKindModifier = "protected"
+ ScriptElementKindModifierExported ScriptElementKindModifier = "export"
+ ScriptElementKindModifierAmbient ScriptElementKindModifier = "declare"
+ ScriptElementKindModifierStatic ScriptElementKindModifier = "static"
+ ScriptElementKindModifierAbstract ScriptElementKindModifier = "abstract"
+ ScriptElementKindModifierOptional ScriptElementKindModifier = "optional"
+ ScriptElementKindModifierDeprecated ScriptElementKindModifier = "deprecated"
+ ScriptElementKindModifierDts ScriptElementKindModifier = ".d.ts"
+ ScriptElementKindModifierTs ScriptElementKindModifier = ".ts"
+ ScriptElementKindModifierTsx ScriptElementKindModifier = ".tsx"
+ ScriptElementKindModifierJs ScriptElementKindModifier = ".js"
+ ScriptElementKindModifierJsx ScriptElementKindModifier = ".jsx"
+ ScriptElementKindModifierJson ScriptElementKindModifier = ".json"
+ ScriptElementKindModifierDmts ScriptElementKindModifier = ".d.mts"
+ ScriptElementKindModifierMts ScriptElementKindModifier = ".mts"
+ ScriptElementKindModifierMjs ScriptElementKindModifier = ".mjs"
+ ScriptElementKindModifierDcts ScriptElementKindModifier = ".d.cts"
+ ScriptElementKindModifierCts ScriptElementKindModifier = ".cts"
+ ScriptElementKindModifierCjs ScriptElementKindModifier = ".cjs"
+)
+
+var fileExtensionKindModifiers = []ScriptElementKindModifier{
+ ScriptElementKindModifierDts,
+ ScriptElementKindModifierTs,
+ ScriptElementKindModifierTsx,
+ ScriptElementKindModifierJs,
+ ScriptElementKindModifierJsx,
+ ScriptElementKindModifierJson,
+ ScriptElementKindModifierDmts,
+ ScriptElementKindModifierMts,
+ ScriptElementKindModifierMjs,
+ ScriptElementKindModifierDcts,
+ ScriptElementKindModifierCts,
+ ScriptElementKindModifierCjs,
+}
+
+func getSymbolKind(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
+ result := getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker, symbol, location)
+ if result != ScriptElementKindUnknown {
+ return result
+ }
+
+ flags := checker.GetCombinedLocalAndExportSymbolFlags(symbol)
+ if flags&ast.SymbolFlagsClass != 0 {
+ decl := ast.GetDeclarationOfKind(symbol, ast.KindClassExpression)
+ if decl != nil {
+ return ScriptElementKindLocalClassElement
+ }
+ return ScriptElementKindClassElement
+ }
+ if flags&ast.SymbolFlagsEnum != 0 {
+ return ScriptElementKindEnumElement
+ }
+ if flags&ast.SymbolFlagsTypeAlias != 0 {
+ return ScriptElementKindTypeElement
+ }
+ if flags&ast.SymbolFlagsInterface != 0 {
+ return ScriptElementKindInterfaceElement
+ }
+ if flags&ast.SymbolFlagsTypeParameter != 0 {
+ return ScriptElementKindTypeParameterElement
+ }
+ if flags&ast.SymbolFlagsEnumMember != 0 {
+ return ScriptElementKindEnumMemberElement
+ }
+ if flags&ast.SymbolFlagsAlias != 0 {
+ return ScriptElementKindAlias
+ }
+ if flags&ast.SymbolFlagsModule != 0 {
+ return ScriptElementKindModuleElement
+ }
+
+ return ScriptElementKindUnknown
+}
+
+func getSymbolKindOfConstructorPropertyMethodAccessorFunctionOrVar(typeChecker *checker.Checker, symbol *ast.Symbol, location *ast.Node) ScriptElementKind {
+ roots := typeChecker.GetRootSymbols(symbol)
+ // If this is a method from a mapped type, leave as a method so long as it still has a call signature, as opposed to e.g.
+ // `{ [K in keyof I]: number }`.
+ if len(roots) == 1 &&
+ roots[0].Flags&ast.SymbolFlagsMethod != 0 &&
+ len(typeChecker.GetCallSignatures(typeChecker.GetNonNullableType(typeChecker.GetTypeOfSymbolAtLocation(symbol, location)))) > 0 {
+ return ScriptElementKindMemberFunctionElement
+ }
+
+ if typeChecker.IsUndefinedSymbol(symbol) {
+ return ScriptElementKindVariableElement
+ }
+ if typeChecker.IsArgumentsSymbol(symbol) {
+ return ScriptElementKindLocalVariableElement
+ }
+ if location.Kind == ast.KindThisKeyword && ast.IsExpression(location) ||
+ ast.IsThisInTypeQuery(location) {
+ return ScriptElementKindParameterElement
+ }
+
+ flags := checker.GetCombinedLocalAndExportSymbolFlags(symbol)
+ if flags&ast.SymbolFlagsVariable != 0 {
+ if isFirstDeclarationOfSymbolParameter(symbol) {
+ return ScriptElementKindParameterElement
+ } else if symbol.ValueDeclaration != nil && ast.IsVarConst(symbol.ValueDeclaration) {
+ return ScriptElementKindConstElement
+ } else if symbol.ValueDeclaration != nil && ast.IsVarUsing(symbol.ValueDeclaration) {
+ return ScriptElementKindVariableUsingElement
+ } else if symbol.ValueDeclaration != nil && ast.IsVarAwaitUsing(symbol.ValueDeclaration) {
+ return ScriptElementKindVariableAwaitUsingElement
+ } else if core.Some(symbol.Declarations, ast.IsLet) {
+ return ScriptElementKindLetElement
+ }
+ if isLocalVariableOrFunction(symbol) {
+ return ScriptElementKindLocalVariableElement
+ }
+ return ScriptElementKindVariableElement
+ }
+ if flags&ast.SymbolFlagsFunction != 0 {
+ if isLocalVariableOrFunction(symbol) {
+ return ScriptElementKindLocalFunctionElement
+ }
+ return ScriptElementKindFunctionElement
+ }
+ // FIXME: getter and setter use the same symbol. And it is rare to use only setter without getter, so in most cases the symbol always has getter flag.
+ // So, even when the location is just on the declaration of setter, this function returns getter.
+ if flags&ast.SymbolFlagsGetAccessor != 0 {
+ return ScriptElementKindMemberGetAccessorElement
+ }
+ if flags&ast.SymbolFlagsSetAccessor != 0 {
+ return ScriptElementKindMemberSetAccessorElement
+ }
+ if flags&ast.SymbolFlagsMethod != 0 {
+ return ScriptElementKindMemberFunctionElement
+ }
+ if flags&ast.SymbolFlagsConstructor != 0 {
+ return ScriptElementKindConstructorImplementationElement
+ }
+ if flags&ast.SymbolFlagsSignature != 0 {
+ return ScriptElementKindIndexSignatureElement
+ }
+
+ if flags&ast.SymbolFlagsProperty != 0 {
+ if flags&ast.SymbolFlagsTransient != 0 &&
+ symbol.CheckFlags&ast.CheckFlagsSynthetic != 0 {
+ // If union property is result of union of non method (property/accessors/variables), it is labeled as property
+ var unionPropertyKind ScriptElementKind
+ for _, rootSymbol := range roots {
+ if rootSymbol.Flags&(ast.SymbolFlagsPropertyOrAccessor|ast.SymbolFlagsVariable) != 0 {
+ unionPropertyKind = ScriptElementKindMemberVariableElement
+ break
+ }
+ }
+ if unionPropertyKind == ScriptElementKindUnknown {
+ // If this was union of all methods,
+ // make sure it has call signatures before we can label it as method.
+ typeOfUnionProperty := typeChecker.GetTypeOfSymbolAtLocation(symbol, location)
+ if len(typeChecker.GetCallSignatures(typeOfUnionProperty)) > 0 {
+ return ScriptElementKindMemberFunctionElement
+ }
+ return ScriptElementKindMemberVariableElement
+ }
+ return unionPropertyKind
+ }
+
+ return ScriptElementKindMemberVariableElement
+ }
+
+ return ScriptElementKindUnknown
+}
+
+func isFirstDeclarationOfSymbolParameter(symbol *ast.Symbol) bool {
+ var declaration *ast.Node
+ if len(symbol.Declarations) > 0 {
+ declaration = symbol.Declarations[0]
+ }
+ result := ast.FindAncestorOrQuit(declaration, func(n *ast.Node) ast.FindAncestorResult {
+ if ast.IsParameter(n) {
+ return ast.FindAncestorTrue
+ }
+ if ast.IsBindingElement(n) || ast.IsObjectBindingPattern(n) || ast.IsArrayBindingPattern(n) {
+ return ast.FindAncestorFalse
+ }
+ return ast.FindAncestorQuit
+ })
+
+ return result != nil
+}
+
+func isLocalVariableOrFunction(symbol *ast.Symbol) bool {
+ if symbol.Parent != nil {
+ return false // This is exported symbol
+ }
+
+ for _, decl := range symbol.Declarations {
+ // Function expressions are local
+ if decl.Kind == ast.KindFunctionExpression {
+ return true
+ }
+
+ if decl.Kind != ast.KindVariableDeclaration && decl.Kind != ast.KindFunctionDeclaration {
+ continue
+ }
+
+ // If the parent is not source file or module block, it is a local variable.
+ for parent := decl.Parent; !ast.IsFunctionBlock(parent); parent = parent.Parent {
+ // Reached source file or module block
+ if parent.Kind == ast.KindSourceFile || parent.Kind == ast.KindModuleBlock {
+ continue
+ }
+ }
+
+ // Parent is in function block.
+ return true
+ }
+ return false
+}
+
+func getSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) core.Set[ScriptElementKindModifier] {
+ if symbol == nil {
+ return core.Set[ScriptElementKindModifier]{}
+ }
+
+ modifiers := getNormalizedSymbolModifiers(typeChecker, symbol)
+ if symbol.Flags&ast.SymbolFlagsAlias != 0 {
+ resolvedSymbol := typeChecker.GetAliasedSymbol(symbol)
+ if resolvedSymbol != symbol {
+ aliasModifiers := getNormalizedSymbolModifiers(typeChecker, resolvedSymbol)
+ for modifier := range aliasModifiers.Keys() {
+ modifiers.Add(modifier)
+ }
+ }
+ }
+ if symbol.Flags&ast.SymbolFlagsOptional != 0 {
+ modifiers.Add(ScriptElementKindModifierOptional)
+ }
+
+ return modifiers
+}
+
+func getNormalizedSymbolModifiers(typeChecker *checker.Checker, symbol *ast.Symbol) core.Set[ScriptElementKindModifier] {
+ var modifierSet core.Set[ScriptElementKindModifier]
+ if len(symbol.Declarations) > 0 {
+ declaration := symbol.Declarations[0]
+ declarations := symbol.Declarations[1:]
+ // omit deprecated flag if some declarations are not deprecated
+ var excludeFlags ast.ModifierFlags
+ if len(declarations) > 0 &&
+ typeChecker.IsDeprecatedDeclaration(declaration) && // !!! include jsdoc node flags
+ core.Some(declarations, func(d *ast.Node) bool { return !typeChecker.IsDeprecatedDeclaration(d) }) {
+ excludeFlags = ast.ModifierFlagsDeprecated
+ } else {
+ excludeFlags = ast.ModifierFlagsNone
+ }
+ modifierSet = getNodeModifiers(declaration, excludeFlags)
+ }
+
+ return modifierSet
+}
+
+func getNodeModifiers(node *ast.Node, excludeFlags ast.ModifierFlags) core.Set[ScriptElementKindModifier] {
+ var result core.Set[ScriptElementKindModifier]
+ var flags ast.ModifierFlags
+ if ast.IsDeclaration(node) {
+ flags = ast.GetCombinedModifierFlags(node) & ^excludeFlags // !!! include jsdoc node flags
+ }
+
+ if flags&ast.ModifierFlagsPrivate != 0 {
+ result.Add(ScriptElementKindModifierPrivate)
+ }
+ if flags&ast.ModifierFlagsProtected != 0 {
+ result.Add(ScriptElementKindModifierProtected)
+ }
+ if flags&ast.ModifierFlagsPublic != 0 {
+ result.Add(ScriptElementKindModifierPublic)
+ }
+ if flags&ast.ModifierFlagsStatic != 0 {
+ result.Add(ScriptElementKindModifierStatic)
+ }
+ if flags&ast.ModifierFlagsAbstract != 0 {
+ result.Add(ScriptElementKindModifierAbstract)
+ }
+ if flags&ast.ModifierFlagsExport != 0 {
+ result.Add(ScriptElementKindModifierExported)
+ }
+ if flags&ast.ModifierFlagsDeprecated != 0 {
+ result.Add(ScriptElementKindModifierDeprecated)
+ }
+ if flags&ast.ModifierFlagsAmbient != 0 {
+ result.Add(ScriptElementKindModifierAmbient)
+ }
+ if node.Flags&ast.NodeFlagsAmbient != 0 {
+ result.Add(ScriptElementKindModifierAmbient)
+ }
+ if node.Kind == ast.KindExportAssignment {
+ result.Add(ScriptElementKindModifierExported)
+ }
+
+ return result
+}
diff --git a/internal/ls/types.go b/internal/ls/types.go
index 830a6bfc3d..67c5028d80 100644
--- a/internal/ls/types.go
+++ b/internal/ls/types.go
@@ -15,3 +15,36 @@ type Location struct {
FileName string
Range core.TextRange
}
+
+type JsxAttributeCompletionStyle string
+
+const (
+ JsxAttributeCompletionStyleAuto JsxAttributeCompletionStyle = "auto"
+ JsxAttributeCompletionStyleBraces JsxAttributeCompletionStyle = "braces"
+ JsxAttributeCompletionStyleNone JsxAttributeCompletionStyle = "none"
+)
+
+type UserPreferences struct {
+ // Enables auto-import-style completions on partially-typed import statements. E.g., allows
+ // `import write|` to be completed to `import { writeFile } from "fs"`.
+ IncludeCompletionsForImportStatements *bool
+
+ // Unless this option is `false`, member completion lists triggered with `.` will include entries
+ // on potentially-null and potentially-undefined values, with insertion text to replace
+ // preceding `.` tokens with `?.`.
+ IncludeAutomaticOptionalChainCompletions *bool
+
+ // If enabled, completions for class members (e.g. methods and properties) will include
+ // a whole declaration for the member.
+ // E.g., `class A { f| }` could be completed to `class A { foo(): number {} }`, instead of
+ // `class A { foo }`.
+ IncludeCompletionsWithClassMemberSnippets *bool
+
+ // If enabled, object literal methods will have a method declaration completion entry in addition
+ // to the regular completion entry containing just the method name.
+ // E.g., `const objectLiteral: T = { f| }` could be completed to `const objectLiteral: T = { foo(): void {} }`,
+ // in addition to `const objectLiteral: T = { foo }`.
+ IncludeCompletionsWithObjectLiteralMethodSnippets *bool
+
+ JsxAttributeCompletionStyle *JsxAttributeCompletionStyle
+}
diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go
new file mode 100644
index 0000000000..460fbbd1b0
--- /dev/null
+++ b/internal/ls/utilities.go
@@ -0,0 +1,377 @@
+package ls
+
+import (
+ "strings"
+
+ "github.com/microsoft/typescript-go/internal/ast"
+ "github.com/microsoft/typescript-go/internal/astnav"
+ "github.com/microsoft/typescript-go/internal/checker"
+ "github.com/microsoft/typescript-go/internal/core"
+ "github.com/microsoft/typescript-go/internal/lsp/lsproto"
+ "github.com/microsoft/typescript-go/internal/scanner"
+)
+
+var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`)
+
+// !!!
+func isInString(file *ast.SourceFile, position int, previousToken *ast.Node) bool {
+ return false
+}
+
+func tryGetImportFromModuleSpecifier(node *ast.StringLiteralLike) *ast.Node {
+ switch node.Parent.Kind {
+ case ast.KindImportDeclaration, ast.KindExportDeclaration, ast.KindJSDocImportTag:
+ return node.Parent
+ case ast.KindExternalModuleReference:
+ return node.Parent.Parent
+ case ast.KindCallExpression:
+ if ast.IsImportCall(node.Parent) || ast.IsRequireCall(node.Parent, false /*requireStringLiteralLikeArgument*/) {
+ return node.Parent
+ }
+ return nil
+ case ast.KindLiteralType:
+ if !ast.IsStringLiteral(node) {
+ return nil
+ }
+ if ast.IsImportTypeNode(node.Parent.Parent) {
+ return node.Parent.Parent
+ }
+ return nil
+ }
+ return nil
+}
+
+// !!!
+func isInComment(file *ast.SourceFile, position int, tokenAtPosition *ast.Node) *ast.CommentRange {
+ return nil
+}
+
+// !!!
+// Replaces last(node.getChildren(sourceFile))
+func getLastChild(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
+ return nil
+}
+
+func getLastToken(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
+ if node == nil {
+ return nil
+ }
+
+ assertHasRealPosition(node)
+
+ lastChild := getLastChild(node, sourceFile)
+ if lastChild == nil {
+ return nil
+ }
+
+ if lastChild.Kind < ast.KindFirstNode {
+ return lastChild
+ } else {
+ return getLastToken(lastChild, sourceFile)
+ }
+}
+
+// !!!
+func getFirstToken(node *ast.Node, sourceFile *ast.SourceFile) *ast.Node {
+ return nil
+}
+
+func assertHasRealPosition(node *ast.Node) {
+ if ast.PositionIsSynthesized(node.Pos()) || ast.PositionIsSynthesized(node.End()) {
+ panic("Node must have a real position for this operation.")
+ }
+}
+
+// !!!
+func findChildOfKind(node *ast.Node, kind ast.Kind, sourceFile *ast.SourceFile) *ast.Node {
+ return nil
+}
+
+// !!!
+type PossibleTypeArgumentInfo struct {
+ called *ast.IdentifierNode
+ nTypeArguments int
+}
+
+// !!!
+func getPossibleTypeArgumentsInfo(tokenIn *ast.Node, sourceFile *ast.SourceFile) *PossibleTypeArgumentInfo {
+ return nil
+}
+
+// !!!
+func getPossibleGenericSignatures(called *ast.Expression, typeArgumentCount int, checker *checker.Checker) []*checker.Signature {
+ return nil
+}
+
+func isInRightSideOfInternalImportEqualsDeclaration(node *ast.Node) bool {
+ for node.Parent.Kind == ast.KindQualifiedName {
+ node = node.Parent
+ }
+
+ return ast.IsInternalModuleImportEqualsDeclaration(node.Parent) && node.Parent.AsImportEqualsDeclaration().ModuleReference == node
+}
+
+func (l *LanguageService) createLspRangeFromNode(node *ast.Node, file *ast.SourceFile) *lsproto.Range {
+ return l.createLspRangeFromBounds(node.Pos(), node.End(), file)
+}
+
+func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.SourceFile) *lsproto.Range {
+ lspRange, err := l.converters.ToLSPRange(file.FileName(), core.NewTextRange(start, end))
+ if err != nil {
+ panic(err)
+ }
+ return &lspRange
+}
+
+func quote(file *ast.SourceFile, preferences *UserPreferences, text string) string {
+ // Editors can pass in undefined or empty string - we want to infer the preference in those cases.
+ quotePreference := getQuotePreference(file, preferences)
+ quoted, _ := core.StringifyJson(text, "" /*prefix*/, "" /*indent*/)
+ if quotePreference == quotePreferenceSingle {
+ quoted = quoteReplacer.Replace(core.StripQuotes(quoted))
+ }
+ return quoted
+}
+
+type quotePreference int
+
+const (
+ quotePreferenceSingle quotePreference = iota
+ quotePreferenceDouble
+)
+
+// !!!
+func getQuotePreference(file *ast.SourceFile, preferences *UserPreferences) quotePreference {
+ return quotePreferenceDouble
+}
+
+func positionIsASICandidate(pos int, context *ast.Node, file *ast.SourceFile) bool {
+ contextAncestor := ast.FindAncestorOrQuit(context, func(ancestor *ast.Node) ast.FindAncestorResult {
+ if ancestor.End() != pos {
+ return ast.FindAncestorQuit
+ }
+
+ return ast.ToFindAncestorResult(syntaxMayBeASICandidate(ancestor.Kind))
+ })
+
+ return contextAncestor != nil && nodeIsASICandidate(contextAncestor, file)
+}
+
+func syntaxMayBeASICandidate(kind ast.Kind) bool {
+ return syntaxRequiresTrailingCommaOrSemicolonOrASI(kind) ||
+ syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind) ||
+ syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind) ||
+ syntaxRequiresTrailingSemicolonOrASI(kind)
+}
+
+func syntaxRequiresTrailingCommaOrSemicolonOrASI(kind ast.Kind) bool {
+ return kind == ast.KindCallSignature ||
+ kind == ast.KindConstructSignature ||
+ kind == ast.KindIndexSignature ||
+ kind == ast.KindPropertySignature ||
+ kind == ast.KindMethodSignature
+}
+
+func syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(kind ast.Kind) bool {
+ return kind == ast.KindFunctionDeclaration ||
+ kind == ast.KindConstructor ||
+ kind == ast.KindMethodDeclaration ||
+ kind == ast.KindGetAccessor ||
+ kind == ast.KindSetAccessor
+}
+
+func syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(kind ast.Kind) bool {
+ return kind == ast.KindModuleDeclaration
+}
+
+func syntaxRequiresTrailingSemicolonOrASI(kind ast.Kind) bool {
+ return kind == ast.KindVariableStatement ||
+ kind == ast.KindExpressionStatement ||
+ kind == ast.KindDoStatement ||
+ kind == ast.KindContinueStatement ||
+ kind == ast.KindBreakStatement ||
+ kind == ast.KindReturnStatement ||
+ kind == ast.KindThrowStatement ||
+ kind == ast.KindDebuggerStatement ||
+ kind == ast.KindPropertyDeclaration ||
+ kind == ast.KindTypeAliasDeclaration ||
+ kind == ast.KindImportDeclaration ||
+ kind == ast.KindImportEqualsDeclaration ||
+ kind == ast.KindExportDeclaration ||
+ kind == ast.KindNamespaceExportDeclaration ||
+ kind == ast.KindExportAssignment
+}
+
+func nodeIsASICandidate(node *ast.Node, file *ast.SourceFile) bool {
+ lastToken := getLastToken(node, file)
+ if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
+ return false
+ }
+
+ if syntaxRequiresTrailingCommaOrSemicolonOrASI(node.Kind) {
+ if lastToken != nil && lastToken.Kind == ast.KindCommaToken {
+ return false
+ }
+ } else if syntaxRequiresTrailingModuleBlockOrSemicolonOrASI(node.Kind) {
+ lastChild := getLastChild(node, file)
+ if lastChild != nil && ast.IsModuleBlock(lastChild) {
+ return false
+ }
+ } else if syntaxRequiresTrailingFunctionBlockOrSemicolonOrASI(node.Kind) {
+ lastChild := getLastChild(node, file)
+ if lastChild != nil && ast.IsFunctionBlock(lastChild) {
+ return false
+ }
+ } else if !syntaxRequiresTrailingSemicolonOrASI(node.Kind) {
+ return false
+ }
+
+ // See comment in parser's `parseDoStatement`
+ if node.Kind == ast.KindDoStatement {
+ return true
+ }
+
+ topNode := ast.FindAncestor(node, func(ancestor *ast.Node) bool { return ancestor.Parent == nil })
+ nextToken := astnav.FindNextToken(node, topNode, file)
+ if nextToken == nil || nextToken.Kind == ast.KindCloseBraceToken {
+ return true
+ }
+
+ startLine, _ := scanner.GetLineAndCharacterOfPosition(file, node.End())
+ endLine, _ := scanner.GetLineAndCharacterOfPosition(file, astnav.GetStartOfNode(nextToken, file, false /*includeJSDoc*/))
+ return startLine != endLine
+}
+
+func isNonContextualKeyword(token ast.Kind) bool {
+ return ast.IsKeywordKind(token) && !ast.IsContextualKeyword(token)
+}
+
+func probablyUsesSemicolons(file *ast.SourceFile) bool {
+ withSemicolon := 0
+ withoutSemicolon := 0
+ nStatementsToObserve := 5
+
+ var visit func(node *ast.Node) bool
+ visit = func(node *ast.Node) bool {
+ if syntaxRequiresTrailingSemicolonOrASI(node.Kind) {
+ lastToken := getLastToken(node, file)
+ if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
+ withSemicolon++
+ } else {
+ withoutSemicolon++
+ }
+ } else if syntaxRequiresTrailingCommaOrSemicolonOrASI(node.Kind) {
+ lastToken := getLastToken(node, file)
+ if lastToken != nil && lastToken.Kind == ast.KindSemicolonToken {
+ withSemicolon++
+ } else if lastToken != nil && lastToken.Kind != ast.KindCommaToken {
+ lastTokenLine, _ := scanner.GetLineAndCharacterOfPosition(
+ file,
+ astnav.GetStartOfNode(lastToken, file, false /*includeJSDoc*/))
+ nextTokenLine, _ := scanner.GetLineAndCharacterOfPosition(
+ file,
+ scanner.GetRangeOfTokenAtPosition(file, lastToken.End()).Pos())
+ // Avoid counting missing semicolon in single-line objects:
+ // `function f(p: { x: string /*no semicolon here is insignificant*/ }) {`
+ if lastTokenLine != nextTokenLine {
+ withoutSemicolon++
+ }
+ }
+ }
+
+ if withSemicolon+withoutSemicolon >= nStatementsToObserve {
+ return true
+ }
+
+ return node.ForEachChild(visit)
+ }
+
+ file.ForEachChild(visit)
+
+ // One statement missing a semicolon isn't sufficient evidence to say the user
+ // doesn't want semicolons, because they may not even be done writing that statement.
+ if withSemicolon == 0 && withoutSemicolon <= 1 {
+ return true
+ }
+
+ // If even 2/5 places have a semicolon, the user probably wants semicolons
+ return withSemicolon/withoutSemicolon > 1/nStatementsToObserve
+}
+
+var typeKeywords *core.Set[ast.Kind] = core.NewSetFromItems(
+ ast.KindAnyKeyword,
+ ast.KindAssertsKeyword,
+ ast.KindBigIntKeyword,
+ ast.KindBooleanKeyword,
+ ast.KindFalseKeyword,
+ ast.KindInferKeyword,
+ ast.KindKeyOfKeyword,
+ ast.KindNeverKeyword,
+ ast.KindNullKeyword,
+ ast.KindNumberKeyword,
+ ast.KindObjectKeyword,
+ ast.KindReadonlyKeyword,
+ ast.KindStringKeyword,
+ ast.KindSymbolKeyword,
+ ast.KindTypeOfKeyword,
+ ast.KindTrueKeyword,
+ ast.KindVoidKeyword,
+ ast.KindUndefinedKeyword,
+ ast.KindUniqueKeyword,
+ ast.KindUnknownKeyword,
+)
+
+func isTypeKeyword(kind ast.Kind) bool {
+ return typeKeywords.Has(kind)
+}
+
+// Returns a map of all names in the file to their positions.
+// !!! cache this
+func getNameTable(file *ast.SourceFile) map[string]int {
+ nameTable := make(map[string]int)
+ var walk func(node *ast.Node) bool
+
+ walk = func(node *ast.Node) bool {
+ if ast.IsIdentifier(node) && !isTagName(node) && node.Text() != "" ||
+ ast.IsStringOrNumericLiteralLike(node) && literalIsName(node) ||
+ ast.IsPrivateIdentifier(node) {
+ text := node.Text()
+ if _, ok := nameTable[text]; ok {
+ nameTable[text] = -1
+ } else {
+ nameTable[text] = node.Pos()
+ }
+ }
+
+ node.ForEachChild(walk)
+ jsdocNodes := node.JSDoc(file)
+ for _, jsdoc := range jsdocNodes {
+ jsdoc.ForEachChild(walk)
+ }
+ return false
+ }
+
+ file.ForEachChild(walk)
+ return nameTable
+}
+
+// We want to store any numbers/strings if they were a name that could be
+// related to a declaration. So, if we have 'import x = require("something")'
+// then we want 'something' to be in the name table. Similarly, if we have
+// "a['propname']" then we want to store "propname" in the name table.
+func literalIsName(node *ast.NumericOrStringLikeLiteral) bool {
+ return ast.IsDeclarationName(node) ||
+ node.Parent.Kind == ast.KindExternalModuleReference ||
+ isArgumentOfElementAccessExpression(node) ||
+ ast.IsLiteralComputedPropertyDeclarationName(node)
+}
+
+func isArgumentOfElementAccessExpression(node *ast.Node) bool {
+ return node != nil && node.Parent != nil &&
+ node.Parent.Kind == ast.KindElementAccessExpression &&
+ node.Parent.AsElementAccessExpression().ArgumentExpression == node
+}
+
+func isTagName(node *ast.Node) bool {
+ return node.Parent != nil && ast.IsJSDocTag(node.Parent) && node.Parent.TagName() == node
+}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index e8373bed21..9cc5dc60df 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
+ "runtime/debug"
"slices"
"strings"
"time"
@@ -194,6 +195,8 @@ func (s *Server) handleMessage(req *lsproto.RequestMessage) error {
return s.handleHover(req)
case *lsproto.DefinitionParams:
return s.handleDefinition(req)
+ case *lsproto.CompletionParams:
+ return s.handleCompletion(req)
default:
switch req.Method {
case lsproto.MethodShutdown:
@@ -250,6 +253,10 @@ func (s *Server) handleInitialize(req *lsproto.RequestMessage) error {
InterFileDependencies: true,
},
},
+ CompletionProvider: &lsproto.CompletionOptions{
+ TriggerCharacters: &ls.TriggerCharacters,
+ // !!! other options
+ },
},
})
}
@@ -377,6 +384,32 @@ func (s *Server) handleDefinition(req *lsproto.RequestMessage) error {
return s.sendResult(req.ID, &lsproto.Definition{Locations: &lspLocations})
}
+func (s *Server) handleCompletion(req *lsproto.RequestMessage) (messageErr error) {
+ params := req.Params.(*lsproto.CompletionParams)
+ file, project := s.getFileAndProject(params.TextDocument.Uri)
+ pos, err := s.converters.LineAndCharacterToPositionForFile(params.Position, file.FileName())
+ if err != nil {
+ return s.sendError(req.ID, err)
+ }
+
+ // !!! remove this after completions is fully ported/tested
+ defer func() {
+ if r := recover(); r != nil {
+ stack := debug.Stack()
+ fmt.Printf("panic obtaining completions: %v\n%s", r, string(stack))
+ messageErr = s.sendResult(req.ID, &lsproto.CompletionList{})
+ }
+ }()
+ // !!! get user preferences
+ list := project.LanguageService().ProvideCompletion(
+ file.FileName(),
+ pos,
+ params.Context,
+ s.initializeParams.Capabilities.TextDocument.Completion,
+ &ls.UserPreferences{})
+ return s.sendResult(req.ID, list)
+}
+
func (s *Server) getFileAndProject(uri lsproto.DocumentUri) (*project.ScriptInfo, *project.Project) {
fileName := ls.DocumentURIToFileName(uri)
return s.projectService.EnsureDefaultProjectForFile(fileName)
diff --git a/internal/parser/parser.go b/internal/parser/parser.go
index c0282c2949..8858d17c31 100644
--- a/internal/parser/parser.go
+++ b/internal/parser/parser.go
@@ -199,7 +199,7 @@ func (p *Parser) initializeState(fileName string, path tspath.Path, sourceText s
p.sourceText = sourceText
p.languageVersion = languageVersion
p.scriptKind = ensureScriptKind(fileName, scriptKind)
- p.languageVariant = getLanguageVariant(p.scriptKind)
+ p.languageVariant = ast.GetLanguageVariant(p.scriptKind)
switch p.scriptKind {
case core.ScriptKindJS, core.ScriptKindJSX:
p.contextFlags = ast.NodeFlagsJavaScriptFile
@@ -5931,7 +5931,7 @@ func (p *Parser) scanClassMemberStart() bool {
// public foo() ... // true
// public @dec blah ... // true; we will then report an error later
// export public ... // true; we will then report an error later
- if isClassMemberModifier(idToken) {
+ if ast.IsClassMemberModifier(idToken) {
return true
}
p.nextToken()
@@ -6342,14 +6342,6 @@ func (p *Parser) skipRangeTrivia(textRange core.TextRange) core.TextRange {
return core.NewTextRange(scanner.SkipTrivia(p.sourceText, textRange.Pos()), textRange.End())
}
-func isClassMemberModifier(token ast.Kind) bool {
- return isParameterPropertyModifier(token) || token == ast.KindStaticKeyword || token == ast.KindOverrideKeyword || token == ast.KindAccessorKeyword
-}
-
-func isParameterPropertyModifier(kind ast.Kind) bool {
- return ast.ModifierToFlag(kind)&ast.ModifierFlagsParameterPropertyModifier != 0
-}
-
func isKeyword(token ast.Kind) bool {
return ast.KindFirstKeyword <= token && token <= ast.KindLastKeyword
}
diff --git a/internal/parser/utilities.go b/internal/parser/utilities.go
index 4692830b1a..b2487f9539 100644
--- a/internal/parser/utilities.go
+++ b/internal/parser/utilities.go
@@ -24,15 +24,6 @@ func ensureScriptKind(fileName string, scriptKind core.ScriptKind) core.ScriptKi
return scriptKind
}
-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 tokenIsIdentifierOrKeyword(token ast.Kind) bool {
return token >= ast.KindIdentifier
}
diff --git a/internal/project/service_test.go b/internal/project/service_test.go
index 90952bb5a7..02bd34c484 100644
--- a/internal/project/service_test.go
+++ b/internal/project/service_test.go
@@ -1,19 +1,14 @@
package project_test
import (
- "fmt"
- "io"
"maps"
- "strings"
- "sync"
"testing"
"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/ls"
"github.com/microsoft/typescript-go/internal/project"
- "github.com/microsoft/typescript-go/internal/vfs"
- "github.com/microsoft/typescript-go/internal/vfs/vfstest"
+ "github.com/microsoft/typescript-go/internal/testutil/projecttestutil"
"gotest.tools/v3/assert"
)
@@ -41,7 +36,7 @@ func TestService(t *testing.T) {
t.Parallel()
t.Run("create configured project", func(t *testing.T) {
t.Parallel()
- service, _ := setup(files)
+ service, _ := projecttestutil.Setup(files)
assert.Equal(t, len(service.Projects()), 0)
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
assert.Equal(t, len(service.Projects()), 1)
@@ -54,7 +49,7 @@ func TestService(t *testing.T) {
t.Run("create inferred project", func(t *testing.T) {
t.Parallel()
- service, _ := setup(files)
+ service, _ := projecttestutil.Setup(files)
service.OpenFile("/home/projects/TS/p1/config.ts", files["/home/projects/TS/p1/config.ts"], core.ScriptKindTS, "")
// Find tsconfig, load, notice config.ts is not included, create inferred project
assert.Equal(t, len(service.Projects()), 2)
@@ -64,7 +59,7 @@ func TestService(t *testing.T) {
t.Run("inferred project for in-memory files", func(t *testing.T) {
t.Parallel()
- service, _ := setup(files)
+ service, _ := projecttestutil.Setup(files)
service.OpenFile("/home/projects/TS/p1/config.ts", files["/home/projects/TS/p1/config.ts"], core.ScriptKindTS, "")
service.OpenFile("^/untitled/ts-nul-authority/Untitled-1", "x", core.ScriptKindTS, "")
service.OpenFile("^/untitled/ts-nul-authority/Untitled-2", "y", core.ScriptKindTS, "")
@@ -81,7 +76,7 @@ func TestService(t *testing.T) {
t.Parallel()
t.Run("update script info eagerly and program lazily", func(t *testing.T) {
t.Parallel()
- service, _ := setup(files)
+ service, _ := projecttestutil.Setup(files)
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
info, proj := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/x.ts")
programBefore := proj.GetProgram()
@@ -94,7 +89,7 @@ func TestService(t *testing.T) {
t.Run("unchanged source files are reused", func(t *testing.T) {
t.Parallel()
- service, _ := setup(files)
+ service, _ := projecttestutil.Setup(files)
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
_, proj := service.EnsureDefaultProjectForFile("/home/projects/TS/p1/src/x.ts")
programBefore := proj.GetProgram()
@@ -107,7 +102,7 @@ func TestService(t *testing.T) {
t.Parallel()
filesCopy := maps.Clone(files)
filesCopy["/home/projects/TS/p1/y.ts"] = `export const y = 2;`
- service, _ := setup(filesCopy)
+ service, _ := projecttestutil.Setup(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/index.ts", filesCopy["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/y.ts") == nil)
@@ -122,14 +117,14 @@ func TestService(t *testing.T) {
t.Parallel()
t.Run("delete a file, close it, recreate it", func(t *testing.T) {
t.Parallel()
- service, host := setup(files)
+ service, host := projecttestutil.Setup(files)
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
assert.Equal(t, service.SourceFileCount(), 2)
filesCopy := maps.Clone(files)
delete(filesCopy, "/home/projects/TS/p1/src/x.ts")
- host.replaceFS(filesCopy)
+ host.ReplaceFS(filesCopy)
service.CloseFile("/home/projects/TS/p1/src/x.ts")
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil)
@@ -137,7 +132,7 @@ func TestService(t *testing.T) {
assert.Equal(t, service.SourceFileCount(), 1)
filesCopy["/home/projects/TS/p1/src/x.ts"] = ``
- host.replaceFS(filesCopy)
+ host.ReplaceFS(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/x.ts", filesCopy["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "")
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
@@ -151,19 +146,19 @@ func TestService(t *testing.T) {
t.Parallel()
filesCopy := maps.Clone(files)
delete(filesCopy, "/home/projects/TS/p1/tsconfig.json")
- service, host := setup(filesCopy)
+ service, host := projecttestutil.Setup(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/x.ts", files["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
service.OpenFile("/home/projects/TS/p1/src/index.ts", files["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
delete(filesCopy, "/home/projects/TS/p1/src/x.ts")
- host.replaceFS(filesCopy)
+ host.ReplaceFS(filesCopy)
service.CloseFile("/home/projects/TS/p1/src/x.ts")
assert.Check(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts") == nil)
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") == nil)
filesCopy["/home/projects/TS/p1/src/x.ts"] = ``
- host.replaceFS(filesCopy)
+ host.ReplaceFS(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/x.ts", filesCopy["/home/projects/TS/p1/src/x.ts"], core.ScriptKindTS, "")
assert.Equal(t, service.GetScriptInfo("/home/projects/TS/p1/src/x.ts").Text(), "")
assert.Check(t, service.Projects()[0].GetProgram().GetSourceFile("/home/projects/TS/p1/src/x.ts") != nil)
@@ -186,7 +181,7 @@ func TestService(t *testing.T) {
},
}`
filesCopy["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";`
- service, _ := setup(filesCopy)
+ service, _ := projecttestutil.Setup(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/index.ts", filesCopy["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
service.OpenFile("/home/projects/TS/p2/src/index.ts", filesCopy["/home/projects/TS/p2/src/index.ts"], core.ScriptKindTS, "")
assert.Equal(t, len(service.Projects()), 2)
@@ -209,7 +204,7 @@ func TestService(t *testing.T) {
}
}`
filesCopy["/home/projects/TS/p2/src/index.ts"] = `import { x } from "../../p1/src/x";`
- service, _ := setup(filesCopy)
+ service, _ := projecttestutil.Setup(filesCopy)
service.OpenFile("/home/projects/TS/p1/src/index.ts", filesCopy["/home/projects/TS/p1/src/index.ts"], core.ScriptKindTS, "")
service.OpenFile("/home/projects/TS/p2/src/index.ts", filesCopy["/home/projects/TS/p2/src/index.ts"], core.ScriptKindTS, "")
assert.Equal(t, len(service.Projects()), 2)
@@ -222,62 +217,3 @@ func TestService(t *testing.T) {
})
})
}
-
-func setup(files map[string]string) (*project.Service, *projectServiceHost) {
- host := newProjectServiceHost(files)
- service := project.NewService(host, project.ServiceOptions{
- Logger: host.logger,
- })
- return service, host
-}
-
-type projectServiceHost struct {
- fs vfs.FS
- mu sync.Mutex
- defaultLibraryPath string
- output strings.Builder
- logger *project.Logger
-}
-
-func newProjectServiceHost(files map[string]string) *projectServiceHost {
- fs := bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/))
- host := &projectServiceHost{
- fs: fs,
- defaultLibraryPath: bundled.LibPath(),
- }
- host.logger = project.NewLogger([]io.Writer{&host.output}, "", project.LogLevelVerbose)
- return host
-}
-
-// DefaultLibraryPath implements project.ProjectServiceHost.
-func (p *projectServiceHost) DefaultLibraryPath() string {
- return p.defaultLibraryPath
-}
-
-// FS implements project.ProjectServiceHost.
-func (p *projectServiceHost) FS() vfs.FS {
- return p.fs
-}
-
-// GetCurrentDirectory implements project.ProjectServiceHost.
-func (p *projectServiceHost) GetCurrentDirectory() string {
- return "/"
-}
-
-// Log implements project.ProjectServiceHost.
-func (p *projectServiceHost) Log(msg ...any) {
- p.mu.Lock()
- defer p.mu.Unlock()
- fmt.Fprintln(&p.output, msg...)
-}
-
-// NewLine implements project.ProjectServiceHost.
-func (p *projectServiceHost) NewLine() string {
- return "\n"
-}
-
-func (p *projectServiceHost) replaceFS(files map[string]string) {
- p.fs = bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/))
-}
-
-var _ project.ServiceHost = (*projectServiceHost)(nil)
diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go
index 99641e26f0..21b8d13603 100644
--- a/internal/scanner/scanner.go
+++ b/internal/scanner/scanner.go
@@ -834,7 +834,7 @@ func (s *Scanner) Scan() ast.Kind {
s.token = ast.KindAtToken
case '\\':
cp := s.peekUnicodeEscape()
- if cp >= 0 && isIdentifierStart(cp, s.languageVersion) {
+ if cp >= 0 && IsIdentifierStart(cp, s.languageVersion) {
s.tokenValue = string(s.scanUnicodeEscape(true)) + s.scanIdentifierParts()
s.token = GetIdentifierToken(s.tokenValue)
} else {
@@ -857,7 +857,7 @@ func (s *Scanner) Scan() ast.Kind {
if s.charAt(1) == '\\' {
s.pos++
cp := s.peekUnicodeEscape()
- if cp >= 0 && isIdentifierStart(cp, s.languageVersion) {
+ if cp >= 0 && IsIdentifierStart(cp, s.languageVersion) {
s.tokenValue = "#" + string(s.scanUnicodeEscape(true)) + s.scanIdentifierParts()
s.token = ast.KindPrivateIdentifier
break
@@ -1082,7 +1082,7 @@ func (s *Scanner) ReScanSlashToken() ast.Kind {
s.pos++
for {
ch, size := s.charAndSize()
- if size == 0 || !isIdentifierPart(ch, s.languageVersion) {
+ if size == 0 || !IsIdentifierPart(ch, s.languageVersion) {
break
}
s.pos += size
@@ -1349,7 +1349,7 @@ func (s *Scanner) ScanJSDocToken() ast.Kind {
case '\\':
s.pos--
cp := s.peekUnicodeEscape()
- if cp >= 0 && isIdentifierStart(cp, s.languageVersion) {
+ if cp >= 0 && IsIdentifierStart(cp, s.languageVersion) {
s.tokenValue = string(s.scanUnicodeEscape(true)) + s.scanIdentifierParts()
s.token = GetIdentifierToken(s.tokenValue)
} else {
@@ -1358,14 +1358,14 @@ func (s *Scanner) ScanJSDocToken() ast.Kind {
return s.token
}
- if isIdentifierStart(ch, s.languageVersion) {
+ if IsIdentifierStart(ch, s.languageVersion) {
char := ch
for {
if s.pos >= len(s.text) {
break
}
char, size = s.charAndSize()
- if !isIdentifierPart(char, s.languageVersion) && char != '-' {
+ if !IsIdentifierPart(char, s.languageVersion) && char != '-' {
break
}
s.pos += size
@@ -1402,11 +1402,11 @@ func (s *Scanner) scanIdentifier(prefixLength int) bool {
s.pos = start + prefixLength
}
ch, size := s.charAndSize()
- if isIdentifierStart(ch, s.languageVersion) {
+ if IsIdentifierStart(ch, s.languageVersion) {
for {
s.pos += size
ch, size = s.charAndSize()
- if !isIdentifierPart(ch, s.languageVersion) {
+ if !IsIdentifierPart(ch, s.languageVersion) {
break
}
}
@@ -1424,13 +1424,13 @@ func (s *Scanner) scanIdentifierParts() string {
start := s.pos
for {
ch, size := s.charAndSize()
- if isIdentifierPart(ch, s.languageVersion) {
+ if IsIdentifierPart(ch, s.languageVersion) {
s.pos += size
continue
}
if ch == '\\' {
escaped := s.peekUnicodeEscape()
- if escaped >= 0 && isIdentifierPart(escaped, s.languageVersion) {
+ if escaped >= 0 && IsIdentifierPart(escaped, s.languageVersion) {
sb.WriteString(s.text[start:s.pos])
sb.WriteRune(s.scanUnicodeEscape(true))
start = s.pos
@@ -1631,7 +1631,7 @@ func (s *Scanner) scanEscapeSequence(flags EscapeSequenceScanningFlags) string {
// case CharacterCodes.paragraphSeparator !!!
return ""
default:
- if flags&EscapeSequenceScanningFlagsAnyUnicodeMode != 0 || flags&EscapeSequenceScanningFlagsRegularExpression != 0 && flags&EscapeSequenceScanningFlagsAnnexB == 0 && isIdentifierPart(ch, s.languageVersion) {
+ if flags&EscapeSequenceScanningFlagsAnyUnicodeMode != 0 || flags&EscapeSequenceScanningFlagsRegularExpression != 0 && flags&EscapeSequenceScanningFlagsAnnexB == 0 && IsIdentifierPart(ch, s.languageVersion) {
s.errorAt(diagnostics.This_character_cannot_be_escaped_in_a_regular_expression, s.pos-2, 2)
}
return string(ch)
@@ -1774,7 +1774,7 @@ func (s *Scanner) scanNumber() ast.Kind {
result = ast.KindNumericLiteral
}
ch, _ := s.charAndSize()
- if isIdentifierStart(ch, s.languageVersion) {
+ if IsIdentifierStart(ch, s.languageVersion) {
idStart := s.pos
id := s.scanIdentifierParts()
if result != ast.KindBigIntLiteral && len(id) == 1 && s.text[idStart] == 'n' {
@@ -1948,7 +1948,7 @@ func IsValidIdentifier(s string, languageVersion core.ScriptTarget) bool {
return false
}
for i, ch := range s {
- if i == 0 && !isIdentifierStart(ch, languageVersion) || i != 0 && !isIdentifierPart(ch, languageVersion) {
+ if i == 0 && !IsIdentifierStart(ch, languageVersion) || i != 0 && !IsIdentifierPart(ch, languageVersion) {
return false
}
}
@@ -1960,11 +1960,11 @@ func isWordCharacter(ch rune) bool {
return stringutil.IsASCIILetter(ch) || stringutil.IsDigit(ch) || ch == '_'
}
-func isIdentifierStart(ch rune, languageVersion core.ScriptTarget) bool {
+func IsIdentifierStart(ch rune, languageVersion core.ScriptTarget) bool {
return stringutil.IsASCIILetter(ch) || ch == '_' || ch == '$' || ch >= utf8.RuneSelf && isUnicodeIdentifierStart(ch, languageVersion)
}
-func isIdentifierPart(ch rune, languageVersion core.ScriptTarget) bool {
+func IsIdentifierPart(ch rune, languageVersion core.ScriptTarget) bool {
return isWordCharacter(ch) || ch == '$' || ch >= utf8.RuneSelf && isUnicodeIdentifierPart(ch, languageVersion)
}
@@ -2016,6 +2016,14 @@ func TokenToString(token ast.Kind) string {
return tokenToText[token]
}
+func StringToToken(s string) ast.Kind {
+ kind, ok := textToToken[s]
+ if ok {
+ return kind
+ }
+ return ast.KindUnknown
+}
+
func GetViableKeywordSuggestions() []string {
result := make([]string, 0, len(textToKeyword))
for text := range textToKeyword {
diff --git a/internal/scanner/utilities.go b/internal/scanner/utilities.go
index 91e883fc91..b397d67640 100644
--- a/internal/scanner/utilities.go
+++ b/internal/scanner/utilities.go
@@ -48,12 +48,12 @@ func DeclarationNameToString(name *ast.Node) string {
func IsIdentifierText(name string, languageVersion core.ScriptTarget) bool {
ch, size := utf8.DecodeRuneInString(name)
- if !isIdentifierStart(ch, languageVersion) {
+ if !IsIdentifierStart(ch, languageVersion) {
return false
}
for i := size; i < len(name); {
ch, size = utf8.DecodeRuneInString(name[i:])
- if !isIdentifierPart(ch, languageVersion) {
+ if !IsIdentifierPart(ch, languageVersion) {
return false
}
i += size
diff --git a/internal/testutil/projecttestutil/projecttestutil.go b/internal/testutil/projecttestutil/projecttestutil.go
new file mode 100644
index 0000000000..c2e324ee4d
--- /dev/null
+++ b/internal/testutil/projecttestutil/projecttestutil.go
@@ -0,0 +1,72 @@
+package projecttestutil
+
+import (
+ "fmt"
+ "io"
+ "strings"
+ "sync"
+
+ "github.com/microsoft/typescript-go/internal/bundled"
+ "github.com/microsoft/typescript-go/internal/project"
+ "github.com/microsoft/typescript-go/internal/vfs"
+ "github.com/microsoft/typescript-go/internal/vfs/vfstest"
+)
+
+type ProjectServiceHost struct {
+ fs vfs.FS
+ mu sync.Mutex
+ defaultLibraryPath string
+ output strings.Builder
+ logger *project.Logger
+}
+
+// DefaultLibraryPath implements project.ProjectServiceHost.
+func (p *ProjectServiceHost) DefaultLibraryPath() string {
+ return p.defaultLibraryPath
+}
+
+// FS implements project.ProjectServiceHost.
+func (p *ProjectServiceHost) FS() vfs.FS {
+ return p.fs
+}
+
+// GetCurrentDirectory implements project.ProjectServiceHost.
+func (p *ProjectServiceHost) GetCurrentDirectory() string {
+ return "/"
+}
+
+// Log implements project.ProjectServiceHost.
+func (p *ProjectServiceHost) Log(msg ...any) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+ fmt.Fprintln(&p.output, msg...)
+}
+
+// NewLine implements project.ProjectServiceHost.
+func (p *ProjectServiceHost) NewLine() string {
+ return "\n"
+}
+
+func (p *ProjectServiceHost) ReplaceFS(files map[string]string) {
+ p.fs = bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/))
+}
+
+var _ project.ServiceHost = (*ProjectServiceHost)(nil)
+
+func Setup(files map[string]string) (*project.Service, *ProjectServiceHost) {
+ host := newProjectServiceHost(files)
+ service := project.NewService(host, project.ServiceOptions{
+ Logger: host.logger,
+ })
+ return service, host
+}
+
+func newProjectServiceHost(files map[string]string) *ProjectServiceHost {
+ fs := bundled.WrapFS(vfstest.FromMap(files, false /*useCaseSensitiveFileNames*/))
+ host := &ProjectServiceHost{
+ fs: fs,
+ defaultLibraryPath: bundled.LibPath(),
+ }
+ host.logger = project.NewLogger([]io.Writer{&host.output}, "", project.LogLevelVerbose)
+ return host
+}