From 4fefdb1e190178d703304d113405fd99b9cf9734 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 13 May 2025 15:44:36 -0700 Subject: [PATCH 01/10] WIP: string completions in string --- internal/ast/ast.go | 2 + internal/ast/utilities.go | 12 ++ internal/checker/checker.go | 36 ++-- internal/checker/flow.go | 2 +- internal/checker/inference.go | 6 +- internal/checker/relater.go | 10 +- internal/checker/types.go | 7 + internal/ls/completions.go | 2 +- internal/ls/string_completions.go | 272 ++++++++++++++++++++++++++++++ internal/ls/utilities.go | 29 +++- 10 files changed, 348 insertions(+), 30 deletions(-) create mode 100644 internal/ls/string_completions.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 225a85010e..5cbd0ab2aa 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -1795,6 +1795,8 @@ type ( ObjectLiteralExpressionNode = Node ConstructorDeclarationNode = Node NamedExportsNode = Node + UnionType = Node + LiteralType = Node ) type ( diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index ea0a4fe29e..9c9d774c61 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -2952,3 +2952,15 @@ func GetPropertyNameForPropertyNameNode(name *Node) string { } panic("Unhandled case in getPropertyNameForPropertyNameNode") } + +func IsStringTextContainingNode(node *Node) bool { + return node.Kind == KindStringLiteral || IsTemplateLiteralKind(node.Kind) +} + +func IsTemplateLiteralKind(kind Kind) bool { + return KindFirstTemplateToken <= kind && kind <= KindLastTemplateToken +} + +func IsTemplateLiteralToken(node *Node) bool { + return IsTemplateLiteralKind(node.Kind) +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 87168d9b0b..515f8b9092 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2396,7 +2396,7 @@ func (c *Checker) checkTypeParameter(node *ast.Node) { c.checkSourceElement(tpNode.DefaultType) typeParameter := c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(node)) // Resolve base constraint to reveal circularity errors - c.getBaseConstraintOfType(typeParameter) + c.GetBaseConstraintOfType(typeParameter) if c.getResolvedTypeParameterDefault(typeParameter) == c.circularConstraintType { c.error(tpNode.DefaultType, diagnostics.Type_parameter_0_has_a_circular_default, c.TypeToString(typeParameter)) } @@ -7593,7 +7593,7 @@ func (c *Checker) isTemplateLiteralContext(node *ast.Node) bool { } func (c *Checker) isTemplateLiteralContextualType(t *Type) bool { - return t.flags&(TypeFlagsStringLiteral|TypeFlagsTemplateLiteral) != 0 || t.flags&TypeFlagsInstantiableNonPrimitive != 0 && c.maybeTypeOfKind(core.OrElse(c.getBaseConstraintOfType(t), c.unknownType), TypeFlagsStringLike) + return t.flags&(TypeFlagsStringLiteral|TypeFlagsTemplateLiteral) != 0 || t.flags&TypeFlagsInstantiableNonPrimitive != 0 && c.maybeTypeOfKind(core.OrElse(c.GetBaseConstraintOfType(t), c.unknownType), TypeFlagsStringLike) } func (c *Checker) checkRegularExpressionLiteral(node *ast.Node) *Type { @@ -10087,7 +10087,7 @@ func (c *Checker) getInstantiationExpressionType(exprType *Type, node *ast.Node) return result } } else if t.flags&TypeFlagsInstantiableNonPrimitive != 0 { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) if constraint != nil { instantiated := getInstantiatedTypePart(constraint) if instantiated != constraint { @@ -11245,7 +11245,7 @@ func (c *Checker) checkPropertyAccessibilityAtLocation(location *ast.Node, isSup if containingType.AsTypeParameter().isThisType { containingType = c.getConstraintOfTypeParameter(containingType) } else { - containingType = c.getBaseConstraintOfType(containingType) + containingType = c.GetBaseConstraintOfType(containingType) } } if containingType == nil || !c.hasBaseType(containingType, enclosingClass) { @@ -15669,7 +15669,7 @@ func (c *Checker) isConstructorType(t *Type) bool { return true } if t.flags&TypeFlagsTypeVariable != 0 { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) return constraint != nil && c.isMixinConstructorType(constraint) } return false @@ -15707,7 +15707,7 @@ func (c *Checker) getConstraintOfType(t *Type) *Type { case t.flags&TypeFlagsConditional != 0: return c.getConstraintOfConditionalType(t) } - return c.getBaseConstraintOfType(t) + return c.GetBaseConstraintOfType(t) } func (c *Checker) getConstraintOfTypeParameter(typeParameter *Type) *Type { @@ -18063,7 +18063,7 @@ func (c *Checker) reportCircularBaseType(node *ast.Node, t *Type) { // A valid base type is `any`, an object type or intersection of object types. func (c *Checker) isValidBaseType(t *Type) bool { if t.flags&TypeFlagsTypeParameter != 0 { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) if constraint != nil { return c.isValidBaseType(constraint) } @@ -20190,7 +20190,7 @@ func (c *Checker) isMappedTypeGenericIndexedAccess(t *Type) bool { func (c *Checker) getApparentType(t *Type) *Type { originalType := t if t.flags&TypeFlagsInstantiable != 0 { - t = c.getBaseConstraintOfType(t) + t = c.GetBaseConstraintOfType(t) if t == nil { t = c.unknownType } @@ -20245,7 +20245,7 @@ func (c *Checker) getResolvedApparentTypeOfMappedType(t *Type) *Type { if c.isGenericMappedType(modifiersType) { baseConstraint = c.getApparentTypeOfMappedType(modifiersType) } else { - baseConstraint = c.getBaseConstraintOfType(modifiersType) + baseConstraint = c.GetBaseConstraintOfType(modifiersType) } if baseConstraint != nil && everyType(baseConstraint, func(t *Type) bool { return c.isArrayOrTupleType(t) || c.isArrayOrTupleOrIntersection(t) }) { return c.instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, t.AsMappedType().mapper)) @@ -23826,7 +23826,7 @@ func (c *Checker) isLiteralOfContextualType(candidateType *Type, contextualType // If the contextual type is a type variable constrained to a primitive type, consider // this a literal context for literals of that primitive type. For example, given a // type parameter 'T extends string', infer string literal types for T. - constraint := c.getBaseConstraintOfType(contextualType) + constraint := c.GetBaseConstraintOfType(contextualType) if constraint == nil { constraint = c.unknownType } @@ -24191,7 +24191,7 @@ func (c *Checker) removeConstrainedTypeVariables(types []*Type) []*Type { } // If every constituent in the type variable's constraint is covered by an intersection of the type // variable and that constituent, remove those intersections and substitute the type variable. - constraint := c.getBaseConstraintOfType(typeVariable) + constraint := c.GetBaseConstraintOfType(typeVariable) if everyType(constraint, func(t *Type) bool { return containsType(primitives, t) }) { i := len(types) for i > 0 { @@ -24414,7 +24414,7 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, if typeVariable.flags&TypeFlagsTypeVariable != 0 && (primitiveType.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 && !c.isGenericStringLikeType(primitiveType) || includes&TypeFlagsIncludesEmptyObject != 0) { // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. - constraint := c.getBaseConstraintOfType(typeVariable) + constraint := c.GetBaseConstraintOfType(typeVariable) // Check that T's constraint is similarly composed of primitive types, the object type, or {}. if constraint != nil && everyType(constraint, c.isPrimitiveOrObjectOrEmptyType) { // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, @@ -25668,14 +25668,14 @@ func (c *Checker) getOrCreateSubstitutionType(baseType *Type, constraint *Type) } func (c *Checker) getBaseConstraintOrType(t *Type) *Type { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) if constraint != nil { return constraint } return t } -func (c *Checker) getBaseConstraintOfType(t *Type) *Type { +func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { if t.flags&(TypeFlagsInstantiableNonPrimitive|TypeFlagsUnionOrIntersection|TypeFlagsTemplateLiteral|TypeFlagsStringMapping) != 0 || c.isGenericTupleType(t) { constraint := c.getResolvedBaseConstraint(t, nil) if constraint != c.noConstraintType && constraint != c.circularConstraintType { @@ -27185,7 +27185,7 @@ func (c *Checker) substituteIndexedMappedType(objectType *Type, index *Type) *Ty // Return true if an indexed access with the given object and index types could access an optional property. func (c *Checker) couldAccessOptionalProperty(objectType *Type, indexType *Type) bool { - indexConstraint := c.getBaseConstraintOfType(indexType) + indexConstraint := c.GetBaseConstraintOfType(indexType) return indexConstraint != nil && core.Some(c.getPropertiesOfType(objectType), func(p *ast.Symbol) bool { return p.Flags&ast.SymbolFlagsOptional != 0 && c.isTypeAssignableTo(c.getLiteralTypeFromProperty(p, TypeFlagsStringOrNumberLiteralOrUnique, false), indexConstraint) }) @@ -28810,7 +28810,7 @@ func (c *Checker) hasTypeFacts(t *Type, mask TypeFacts) bool { func (c *Checker) getTypeFactsWorker(t *Type, callerOnlyNeeds TypeFacts) TypeFacts { if t.flags&(TypeFlagsIntersection|TypeFlagsInstantiable) != 0 { - t = c.getBaseConstraintOfType(t) + t = c.GetBaseConstraintOfType(t) if t == nil { t = c.unknownType } @@ -29225,7 +29225,7 @@ func (c *Checker) isAwaitedTypeNeeded(t *Type) bool { } // We only need `Awaited` if `T` contains possibly non-primitive types. if c.isGenericObjectType(t) { - baseConstraint := c.getBaseConstraintOfType(t) + baseConstraint := c.GetBaseConstraintOfType(t) // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, // or is promise-like. if baseConstraint != nil { @@ -29389,7 +29389,7 @@ func (c *Checker) getNonUndefinedType(t *Type) *Type { func (c *Checker) isGenericTypeWithUndefinedConstraint(t *Type) bool { if t.flags&TypeFlagsInstantiable != 0 { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) if constraint != nil { return c.maybeTypeOfKind(constraint, TypeFlagsUndefined) } diff --git a/internal/checker/flow.go b/internal/checker/flow.go index e8d9daec2e..7ad7fd2396 100644 --- a/internal/checker/flow.go +++ b/internal/checker/flow.go @@ -912,7 +912,7 @@ func (c *Checker) getNarrowedTypeWorker(t *Type, candidate *Type, assumeTrue boo } return c.mapType(t, func(t *Type) *Type { if c.maybeTypeOfKind(t, TypeFlagsInstantiable) { - constraint := c.getBaseConstraintOfType(t) + constraint := c.GetBaseConstraintOfType(t) if constraint == nil || isRelated(n, constraint) { return c.getIntersectionType([]*Type{t, n}) } diff --git a/internal/checker/inference.go b/internal/checker/inference.go index 80e465aada..ea506f51c6 100644 --- a/internal/checker/inference.go +++ b/internal/checker/inference.go @@ -520,7 +520,7 @@ func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, ta // allowed template literal placeholder types, infer from a literal type corresponding to the constraint. if source.flags&TypeFlagsStringLiteral != 0 && target.flags&TypeFlagsTypeVariable != 0 { if inferenceContext := getInferenceInfoForType(n, target); inferenceContext != nil { - if constraint := c.getBaseConstraintOfType(inferenceContext.typeParameter); constraint != nil && !IsTypeAny(constraint) { + if constraint := c.GetBaseConstraintOfType(inferenceContext.typeParameter); constraint != nil && !IsTypeAny(constraint) { allTypeFlags := TypeFlagsNone for _, t := range constraint.Distributed() { allTypeFlags |= t.flags @@ -702,7 +702,7 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target * // Middle of target is [...T, ...rest] and source is tuple type // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T if info := getInferenceInfoForType(n, elementTypes[startLength]); info != nil { - constraint := c.getBaseConstraintOfType(info.typeParameter) + constraint := c.GetBaseConstraintOfType(info.typeParameter) if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 { impliedArity := constraint.TargetTupleType().fixedLength c.inferFromTypes(n, c.sliceTupleType(source, startLength, sourceArity-(startLength+impliedArity)), elementTypes[startLength]) @@ -713,7 +713,7 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target * // Middle of target is [...rest, ...T] and source is tuple type // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T if info := getInferenceInfoForType(n, elementTypes[startLength+1]); info != nil { - constraint := c.getBaseConstraintOfType(info.typeParameter) + constraint := c.GetBaseConstraintOfType(info.typeParameter) if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 { impliedArity := constraint.TargetTupleType().fixedLength endIndex := sourceArity - getEndElementCount(target.TargetTupleType(), ElementFlagsFixed) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 56dfb8cdd8..92f0bf605a 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -2837,7 +2837,7 @@ func (r *Relater) unionOrIntersectionRelatedTo(source *Type, target *Type, repor if r.relation == r.c.comparableRelation && target.flags&TypeFlagsPrimitive != 0 { constraints := core.SameMap(source.Types(), func(t *Type) *Type { if t.flags&TypeFlagsInstantiable != 0 { - constraint := r.c.getBaseConstraintOfType(t) + constraint := r.c.GetBaseConstraintOfType(t) if constraint != nil { return constraint } @@ -3704,7 +3704,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo } case source.flags&TypeFlagsTemplateLiteral != 0 && target.flags&TypeFlagsObject == 0: if target.flags&TypeFlagsTemplateLiteral == 0 { - constraint := r.c.getBaseConstraintOfType(source) + constraint := r.c.GetBaseConstraintOfType(source) if constraint != nil && constraint != source { result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors) if result != TernaryFalse { @@ -3722,7 +3722,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo return result } } else { - constraint := r.c.getBaseConstraintOfType(source) + constraint := r.c.GetBaseConstraintOfType(source) if constraint != nil { result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors) if result != TernaryFalse { @@ -4678,7 +4678,7 @@ func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type targetFlags = target.flags } if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck { - constraint := r.c.getBaseConstraintOfType(target) + constraint := r.c.GetBaseConstraintOfType(target) switch { case constraint != nil && r.c.isTypeAssignableTo(generalizedSource, constraint): r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, generalizedSourceType, targetType, r.c.TypeToString(constraint)) @@ -4880,7 +4880,7 @@ func (c *Checker) isTypeDerivedFrom(source *Type, target *Type) bool { return c.isTypeDerivedFrom(t, target) }) case source.flags&TypeFlagsInstantiableNonPrimitive != 0: - constraint := c.getBaseConstraintOfType(source) + constraint := c.GetBaseConstraintOfType(source) if constraint == nil { constraint = c.unknownType } diff --git a/internal/checker/types.go b/internal/checker/types.go index 2e45361759..c816bfd867 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -680,6 +680,10 @@ func (t *Type) IsClass() bool { return t.objectFlags&ObjectFlagsClass != 0 } +func (t *Type) IsTypeParameter() bool { + return t.flags&TypeFlagsTypeParameter != 0 +} + // TypeData type TypeData interface { @@ -1216,3 +1220,6 @@ var LanguageFeatureMinimumTarget = LanguageFeatureMinimumTargetMap{ ClassAndClassElementDecorators: core.ScriptTargetESNext, RegularExpressionFlagsUnicodeSets: core.ScriptTargetESNext, } + +// Aliases for types +type StringLiteralType = Type diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 257b4b2d87..0ae4892b34 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -276,7 +276,7 @@ func (l *LanguageService) getCompletionsAtPosition( clientOptions *lsproto.CompletionClientCapabilities, ) *lsproto.CompletionList { _, previousToken := getRelevantTokens(position, file) - if context.TriggerCharacter != nil && !isInString(file, position, previousToken) && !isValidTrigger(file, *context.TriggerCharacter, previousToken, position) { + if context.TriggerCharacter != nil && !IsInString(file, position, previousToken) && !isValidTrigger(file, *context.TriggerCharacter, previousToken, position) { return nil } diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go new file mode 100644 index 0000000000..571092b2f6 --- /dev/null +++ b/internal/ls/string_completions.go @@ -0,0 +1,272 @@ +package ls + +import ( + "fmt" + "slices" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" +) + +type completionsFromTypes struct { + types []*checker.StringLiteralType + isNewIdentifier bool +} + +type completionsFromProperties struct { + symbols []*ast.Symbol + hasIndexSignature bool +} + +// !!! +type stringLiteralCompletions = any + +func (l *LanguageService) getStringLiteralCompletions( + file *ast.SourceFile, + position int, + contextToken *ast.Node, + compilerOptions *core.CompilerOptions, + program *compiler.Program, + preferences *UserPreferences, +) *lsproto.CompletionList { + // !!! reference comment + if IsInString(file, position, contextToken) { + if contextToken == nil || !ast.IsStringOrNumericLiteralLike(contextToken) { + return nil + } + entries := l.getStringLiteralCompletionEntries( + file, + contextToken, + position, + program, + preferences) + // return l.convertStringLiteralCompletions(entries) // !!! HERE + } + return nil +} + +func (l *LanguageService) getStringLiteralCompletionEntries( + file *ast.SourceFile, + node *ast.StringLiteralLike, + position int, + program *compiler.Program, + preferences *UserPreferences, +) stringLiteralCompletions { + typeChecker := program.GetTypeChecker() + parent := walkUpParentheses(node.Parent) + switch parent.Kind { + case ast.KindLiteralType: + grandparent := walkUpParentheses(parent.Parent) + if grandparent.Kind == ast.KindImportType { + return getStringLiteralCompletionsFromModuleNames( + file, + node, + program, + preferences, + ) + } + return fromUnionableLiteralType(grandparent, parent, position, typeChecker) + case ast.KindPropertyAssignment: + if ast.IsObjectLiteralExpression(parent.Parent) && parent.Name() == node { + // Get quoted name of properties of the object literal expression + // i.e. interface ConfigFiles { + // 'jspm:dev': string + // } + // let files: ConfigFiles = { + // '/*completion position*/' + // } + // + // function foo(c: ConfigFiles) {} + // foo({ + // '/*completion position*/' + // }); + return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent) + } + result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) + if result != nil { + return result + } + return fromContextualType(checker.ContextFlagsNone, node, typeChecker) + case ast.KindElementAccessExpression: + expression := parent.Expression() + argumentExpression := parent.AsElementAccessExpression().ArgumentExpression + if node == ast.SkipParentheses(argumentExpression) { + // Get all names of properties on the expression + // i.e. interface A { + // 'prop1': string + // } + // let a: A; + // a['/*completion position*/'] + t := typeChecker.GetTypeAtLocation(expression) + return stringLiteralCompletionsFromProperties(t, typeChecker) + } + return nil + // !!! HERE + } +} + +func fromContextualType(contextFlags checker.ContextFlags, node *ast.Node, typeChecker *checker.Checker) *completionsFromTypes { + // Get completion for string literal from string literal type + // i.e. var x: "hi" | "hello" = "/*completion position*/" + types := getStringLiteralTypes(getContextualTypeFromParent(node, typeChecker, contextFlags), nil, typeChecker) + if len(types) == 0 { + return nil + } + return &completionsFromTypes{ + types: types, + isNewIdentifier: false, + } +} + +func fromUnionableLiteralType(grandparent *ast.Node, parent *ast.Node, position int, typeChecker *checker.Checker) stringLiteralCompletions { + switch grandparent.Kind { + case ast.KindExpressionWithTypeArguments, ast.KindTypeReference: + typeArgument := ast.FindAncestor(parent, func(n *ast.Node) bool { return n.Parent == grandparent }) + if typeArgument != nil { + t := typeChecker.GetTypeArgumentConstraint(typeArgument) + return &completionsFromTypes{ + types: getStringLiteralTypes(t, nil, typeChecker), + isNewIdentifier: false, + } + } + return nil + case ast.KindIndexedAccessType: + // Get all apparent property names + // i.e. interface Foo { + // foo: string; + // bar: string; + // } + // let x: Foo["/*completion position*/"] + indexType := grandparent.AsIndexedAccessTypeNode().IndexType + objectType := grandparent.AsIndexedAccessTypeNode().ObjectType + if !indexType.Loc.ContainsInclusive(position) { + return nil + } + t := typeChecker.GetTypeFromTypeNode(objectType) + return stringLiteralCompletionsFromProperties(t, typeChecker) + case ast.KindUnionType: + result := fromUnionableLiteralType( + walkUpParentheses(grandparent.Parent), + parent, + position, + typeChecker) + if result == nil { + return nil + } + alreadyUsedTypes := getAlreadyUsedTypesInStringLiteralUnion(grandparent, parent) + switch result := result.(type) { + case *completionsFromProperties: + return &completionsFromProperties{ + symbols: core.Filter( + result.symbols, + func(s *ast.Symbol) bool { return !slices.Contains(alreadyUsedTypes, s.Name) }, + ), + hasIndexSignature: result.hasIndexSignature, + } + case *completionsFromTypes: + return &completionsFromTypes{ + types: core.Filter(result.types, func(t *checker.StringLiteralType) bool { + return !slices.Contains(alreadyUsedTypes, t.AsLiteralType().Value().(string)) + }), + isNewIdentifier: false, + } + default: + panic(fmt.Sprintf("Unexpected result type: %T", result)) + } + default: + return nil + } +} + +func stringLiteralCompletionsForObjectLiteral( + typeChecker *checker.Checker, + objectLiteralExpression *ast.ObjectLiteralExpressionNode) *completionsFromProperties { + contextualType := typeChecker.GetContextualType(objectLiteralExpression, checker.ContextFlagsNone) + if contextualType == nil { + return nil + } + + completionsType := typeChecker.GetContextualType(objectLiteralExpression, checker.ContextFlagsCompletions) + symbols := getPropertiesForObjectExpression( + contextualType, + completionsType, + objectLiteralExpression, + typeChecker) + + return &completionsFromProperties{ + symbols: symbols, + hasIndexSignature: hasIndexSignature(contextualType, typeChecker), + } +} + +func stringLiteralCompletionsFromProperties(t *checker.Type, typeChecker *checker.Checker) *completionsFromProperties { + return &completionsFromProperties{ + symbols: core.Filter(typeChecker.GetApparentProperties(t), func(s *ast.Symbol) bool { + return !(s.ValueDeclaration != nil && ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration)) + }), + hasIndexSignature: hasIndexSignature(t, typeChecker), + } +} + +func getStringLiteralCompletionsFromModuleNames( + file *ast.SourceFile, + node *ast.LiteralExpression, + program *compiler.Program, + preferences *UserPreferences) stringLiteralCompletions { + // !!! here + return nil +} + +func walkUpParentheses(node *ast.Node) *ast.Node { + switch node.Kind { + case ast.KindParenthesizedType: + return ast.WalkUpParenthesizedTypes(node) + case ast.KindParenthesizedExpression: + return ast.WalkUpParenthesizedExpressions(node) + default: + return node + } +} + +func getStringLiteralTypes(t *checker.Type, uniques *core.Set[string], typeChecker *checker.Checker) []*checker.StringLiteralType { + if t == nil { + return nil + } + if uniques == nil { + uniques = &core.Set[string]{} + } + t = skipConstraint(t, typeChecker) + if t.IsUnion() { + var types []*checker.StringLiteralType + for _, elementType := range t.Types() { + types = append(types, getStringLiteralTypes(elementType, uniques, typeChecker)...) + } + return types + } + if t.IsStringLiteral() && !t.IsEnumLiteral() && uniques.AddIfAbsent(t.AsLiteralType().Value().(string)) { + return []*checker.StringLiteralType{t} + } + return nil +} + +func getAlreadyUsedTypesInStringLiteralUnion(union *ast.UnionType, current *ast.LiteralType) []string { + typesList := union.AsUnionTypeNode().Types + if typesList == nil { + return nil + } + var values []string + for _, typeNode := range typesList.Nodes { + if typeNode != current && ast.IsLiteralTypeNode(typeNode) && + ast.IsStringLiteral(typeNode.AsLiteralTypeNode().Literal) { + values = append(values, typeNode.AsLiteralTypeNode().Literal.Text()) + } + } + return values +} + +func hasIndexSignature(t *checker.Type, typeChecker *checker.Checker) bool { + return typeChecker.GetStringIndexType(t) != nil || typeChecker.GetNumberIndexType(t) != nil +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 02286e0b8c..aecffa380e 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -13,8 +13,23 @@ import ( var quoteReplacer = strings.NewReplacer("'", `\'`, `\"`, `"`) -// !!! -func isInString(file *ast.SourceFile, position int, previousToken *ast.Node) bool { +func IsInString(sourceFile *ast.SourceFile, position int, previousToken *ast.Node) bool { + if previousToken != nil && ast.IsStringTextContainingNode(previousToken) { + start := astnav.GetStartOfNode(previousToken, sourceFile, false /*includeJSDoc*/) + end := previousToken.End() + + // To be "in" one of these literals, the position has to be: + // 1. entirely within the token text. + // 2. at the end position of an unterminated token. + // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). + if start < position && position < end { + return true + } + + if position == end { + return ast.IsUnterminatedLiteral(previousToken) + } + } return false } @@ -724,3 +739,13 @@ func getAllSuperTypeNodes(node *ast.Node) []*ast.TypeNode { } return nil } + +func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type { + if t.IsTypeParameter() { + c := typeChecker.GetBaseConstraintOfType(t) + if c != nil { + return c + } + } + return t +} From 4c37a1f50aa1d340e058fe1cb70607d47d847ab3 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 16 May 2025 13:24:38 -0700 Subject: [PATCH 02/10] string completions in non-comment --- internal/checker/services.go | 28 +++ internal/ls/completions.go | 37 ++-- internal/ls/string_completions.go | 280 ++++++++++++++++++++++++++++- internal/ls/utilities.go | 83 +++++++++ internal/printer/printer.go | 6 +- internal/printer/utilities.go | 30 ++-- internal/printer/utilities_test.go | 66 +++---- 7 files changed, 462 insertions(+), 68 deletions(-) diff --git a/internal/checker/services.go b/internal/checker/services.go index 9fd5a8b0a9..7cc296dfc5 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -481,3 +481,31 @@ func (c *Checker) GetJsxIntrinsicTagNamesAt(location *ast.Node) []*ast.Symbol { func (c *Checker) GetContextualTypeForJsxAttribute(attribute *ast.JsxAttributeLike) *Type { return c.getContextualTypeForJsxAttribute(attribute, ContextFlagsNone) } + +func (c *Checker) GetConstantValue(node *ast.Node) any { + if node.Kind == ast.KindEnumMember { + return c.getEnumMemberValue(node).Value + } + + if c.symbolNodeLinks.Get(node).resolvedSymbol == nil { + c.checkExpressionCached(node) // ensure cached resolved symbol is set + } + symbol := c.symbolNodeLinks.Get(node).resolvedSymbol + if symbol == nil && ast.IsEntityNameExpression(node) { + symbol = c.resolveEntityName( + node, + ast.SymbolFlagsValue, + true, /*ignoreErrors*/ + false, /*dontResolveAlias*/ + nil /*location*/) + } + if symbol != nil && symbol.Flags&ast.SymbolFlagsEnumMember != 0 { + // inline property\index accesses only for const enums + member := symbol.ValueDeclaration + if ast.IsEnumConst(member.Parent) { + return c.getEnumMemberValue(member).Value + } + } + + return nil +} diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 0ae4892b34..a83247b956 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -294,7 +294,18 @@ func (l *LanguageService) getCompletionsAtPosition( // !!! see if incomplete completion list and continue or clean - // !!! string literal completions + stringCompletions := l.getStringLiteralCompletions( + file, + position, + previousToken, + compilerOptions, + program, + preferences, + clientOptions, + ) + if stringCompletions != nil { + return stringCompletions + } // !!! label completions @@ -1501,9 +1512,11 @@ func (l *LanguageService) completionInfoFromData( return nil } + optionalReplacementSpan := l.getOptionalReplacementSpan(data.location, file) uniqueNames, sortedEntries := l.getCompletionEntriesFromSymbols( ctx, data, + optionalReplacementSpan, nil, /*replacementToken*/ position, file, @@ -1565,6 +1578,7 @@ func (l *LanguageService) completionInfoFromData( func (l *LanguageService) getCompletionEntriesFromSymbols( ctx context.Context, data *completionDataData, + optionalReplacementSpan *lsproto.Range, replacementToken *ast.Node, position int, file *ast.SourceFile, @@ -1579,7 +1593,6 @@ func (l *LanguageService) getCompletionEntriesFromSymbols( typeChecker, done := program.GetTypeCheckerForFile(ctx, file) defer done() isMemberCompletion := isMemberCompletionKind(data.completionKind) - optionalReplacementSpan := getOptionalReplacementSpan(data.location, file) // 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. @@ -1703,7 +1716,7 @@ func (l *LanguageService) createCompletionItem( preferences *UserPreferences, clientOptions *lsproto.CompletionClientCapabilities, isMemberCompletion bool, - optionalReplacementSpan *core.TextRange, + optionalReplacementSpan *lsproto.Range, ) *lsproto.CompletionItem { contextToken := data.contextToken var insertText string @@ -3105,12 +3118,11 @@ func getJSCompletionEntries( return sortedEntries } -func getOptionalReplacementSpan(location *ast.Node, file *ast.SourceFile) *core.TextRange { +func (l *LanguageService) getOptionalReplacementSpan(location *ast.Node, file *ast.SourceFile) *lsproto.Range { // StringLiteralLike locations are handled separately in stringCompletions.ts if location != nil && location.Kind == ast.KindIdentifier { start := astnav.GetStartOfNode(location, file, false /*includeJSDoc*/) - textRange := core.NewTextRange(start, location.End()) - return &textRange + return l.createLspRangeFromBounds(start, location.End(), file) } return nil } @@ -3929,7 +3941,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( tagName := jsxClosingElement.Parent.AsJsxElement().OpeningElement.TagName() closingTag := tagName.Text() fullClosingTag := closingTag + core.IfElse(hasClosingAngleBracket, "", ">") - optionalReplacementSpan := core.NewTextRange(jsxClosingElement.TagName().Pos(), jsxClosingElement.TagName().End()) + optionalReplacementSpan := l.createLspRangeFromNode(jsxClosingElement.TagName(), file) defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/) item := l.createLSPCompletionItem( @@ -3940,7 +3952,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( ScriptElementKindClassElement, core.Set[ScriptElementKindModifier]{}, /*kindModifiers*/ nil, /*replacementSpan*/ - &optionalReplacementSpan, + optionalReplacementSpan, nil, /*commitCharacters*/ nil, /*labelDetails*/ file, @@ -3970,7 +3982,7 @@ func (l *LanguageService) createLSPCompletionItem( elementKind ScriptElementKind, kindModifiers core.Set[ScriptElementKindModifier], replacementSpan *lsproto.Range, - optionalReplacementSpan *core.TextRange, + optionalReplacementSpan *lsproto.Range, commitCharacters *[]string, labelDetails *lsproto.CompletionItemLabelDetails, file *ast.SourceFile, @@ -3996,8 +4008,11 @@ func (l *LanguageService) createLSPCompletionItem( } else { // Ported from vscode ts extension. if optionalReplacementSpan != nil && ptrIsTrue(clientOptions.CompletionItem.InsertReplaceSupport) { - insertRange := l.createLspRangeFromBounds(optionalReplacementSpan.Pos(), position, file) - replaceRange := l.createLspRangeFromBounds(optionalReplacementSpan.Pos(), optionalReplacementSpan.End(), file) + insertRange := &lsproto.Range{ + Start: optionalReplacementSpan.Start, + End: l.createLspPosition(position, file), + } + replaceRange := optionalReplacementSpan textEdit = &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: core.IfElse(insertText == "", name, insertText), diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 571092b2f6..6101182ae5 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -3,12 +3,15 @@ package ls import ( "fmt" "slices" + "strings" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/printer" + "github.com/microsoft/typescript-go/internal/tspath" ) type completionsFromTypes struct { @@ -21,7 +24,17 @@ type completionsFromProperties struct { hasIndexSignature bool } -// !!! +type completionsFromPaths = []*pathCompletion + +type pathCompletion struct { + name string + // ScriptElementKindScriptElement | ScriptElementKindDirectory | ScriptElementKindExternalModuleName + kind ScriptElementKind + extension string + textRange *core.TextRange +} + +// *completionsFromTypes | *completionsFromProperties | completionsFromPaths type stringLiteralCompletions = any func (l *LanguageService) getStringLiteralCompletions( @@ -31,6 +44,7 @@ func (l *LanguageService) getStringLiteralCompletions( compilerOptions *core.CompilerOptions, program *compiler.Program, preferences *UserPreferences, + clientOptions *lsproto.CompletionClientCapabilities, ) *lsproto.CompletionList { // !!! reference comment if IsInString(file, position, contextToken) { @@ -43,11 +57,148 @@ func (l *LanguageService) getStringLiteralCompletions( position, program, preferences) - // return l.convertStringLiteralCompletions(entries) // !!! HERE + return l.convertStringLiteralCompletions( + entries, + contextToken, + file, + position, + program, + compilerOptions, + preferences, + clientOptions, + ) } return nil } +func (l *LanguageService) convertStringLiteralCompletions( + completion stringLiteralCompletions, + contextToken *ast.StringLiteralLike, + file *ast.SourceFile, + position int, + program *compiler.Program, + options *core.CompilerOptions, + preferences *UserPreferences, + clientOptions *lsproto.CompletionClientCapabilities, +) *lsproto.CompletionList { + if completion == nil { + return nil + } + + optionalReplacementRange := l.createRangeFromStringLiteralLikeContent(file, contextToken, position) + switch completion := completion.(type) { + case completionsFromPaths: + return l.convertPathCompletions(completion, file, position, clientOptions) + case *completionsFromProperties: + data := &completionDataData{ + symbols: completion.symbols, + completionKind: CompletionKindString, + isNewIdentifierLocation: completion.hasIndexSignature, + location: file.AsNode(), + contextToken: contextToken, + } + _, items := l.getCompletionEntriesFromSymbols( + data, + optionalReplacementRange, + contextToken, /*replacementToken*/ + position, + file, + program, + core.ScriptTargetESNext, + preferences, + options, + clientOptions, + ) + defaultCommitCharacters := getDefaultCommitCharacters(completion.hasIndexSignature) + itemDefaults := setCommitCharacters(clientOptions, items, &defaultCommitCharacters) + return &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: items, + } + case *completionsFromTypes: + var quoteChar printer.QuoteChar + if contextToken.Kind == ast.KindNoSubstitutionTemplateLiteral { + quoteChar = printer.QuoteCharBacktick + } else if strings.HasPrefix(contextToken.Text(), "'") { + quoteChar = printer.QuoteCharSingleQuote + } else { + quoteChar = printer.QuoteCharDoubleQuote + } + items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem { + name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar) + return l.createLSPCompletionItem( + name, + "", /*insertText*/ + "", /*filterText*/ + SortTextLocationPriority, + ScriptElementKindString, + core.Set[ScriptElementKindModifier]{}, + l.getReplacementRangeForContextToken(file, contextToken, position), + nil, /*optionalReplacementSpan*/ + nil, /*commitCharacters*/ + nil, /*labelDetails*/ + file, + position, + clientOptions, + false, /*isMemberCompletion*/ + false, /*isSnippet*/ + false, /*hasAction*/ + false, /*preselect*/ + "", /*source*/ + ) + }) + defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier) + itemDefaults := setCommitCharacters(clientOptions, items, &defaultCommitCharacters) + return &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: items, + } + default: + panic(fmt.Sprintf("Unexpected completion type: %T", completion)) + } +} + +func (l *LanguageService) convertPathCompletions( + pathCompletions completionsFromPaths, + file *ast.SourceFile, + position int, + clientOptions *lsproto.CompletionClientCapabilities, +) *lsproto.CompletionList { + isNewIdentifierLocation := true // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. + defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation) + items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *lsproto.CompletionItem { + replacementSpan := l.createLspRangeFromBounds(pathCompletion.textRange.Pos(), pathCompletion.textRange.End(), file) + return l.createLSPCompletionItem( + pathCompletion.name, + "", /*insertText*/ + "", /*filterText*/ + SortTextLocationPriority, + pathCompletion.kind, + *core.NewSetFromItems(kindModifiersFromExtension(pathCompletion.extension)), + replacementSpan, + nil, /*optionalReplacementSpan*/ + nil, /*commitCharacters*/ + nil, /*labelDetails*/ + file, + position, + clientOptions, + false, /*isMemberCompletion*/ + false, /*isSnippet*/ + false, /*hasAction*/ + false, /*preselect*/ + "", /*source*/ + ) + }) + itemDefaults := setCommitCharacters(clientOptions, items, &defaultCommitCharacters) + return &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: items, + } +} + func (l *LanguageService) getStringLiteralCompletionEntries( file *ast.SourceFile, node *ast.StringLiteralLike, @@ -104,7 +255,78 @@ func (l *LanguageService) getStringLiteralCompletionEntries( return stringLiteralCompletionsFromProperties(t, typeChecker) } return nil - // !!! HERE + case ast.KindCallExpression, ast.KindNewExpression, ast.KindJsxAttribute: + if !isRequireCallArgument(node) && !ast.IsImportCall(parent) { + // !!! signature help + // const argumentInfo = SignatureHelp.getArgumentInfoForCompletions(parent.kind === SyntaxKind.JsxAttribute ? parent.parent : node, position, sourceFile, typeChecker); + // // Get string literal completions from specialized signatures of the target + // // i.e. declare function f(a: 'A'); + // // f("/*completion position*/") + // return argumentInfo && getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker) || fromContextualType(ContextFlags.None); + return nil + } + fallthrough // is `require("")` or `require(""` or `import("")` + case ast.KindImportDeclaration, ast.KindExportDeclaration, ast.KindExternalModuleReference, ast.KindJSDocImportTag: + // Get all known external module names or complete a path to a module + // i.e. import * as ns from "/*completion position*/"; + // var y = import("/*completion position*/"); + // import x = require("/*completion position*/"); + // var y = require("/*completion position*/"); + // export * from "/*completion position*/"; + return getStringLiteralCompletionsFromModuleNames(file, node, program, preferences) + case ast.KindCaseClause: + tracker := newCaseClauseTracker(typeChecker, parent.Parent.AsCaseBlock().Clauses.Nodes) + contextualTypes := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) + if contextualTypes == nil { + return nil + } + literals := core.Filter(contextualTypes.types, func(t *checker.StringLiteralType) bool { + return !tracker.hasValue(t.AsLiteralType().Value()) + }) + return &completionsFromTypes{ + types: literals, + isNewIdentifier: false, + } + case ast.KindImportSpecifier, ast.KindExportSpecifier: + // Complete string aliases in `import { "|" } from` and `export { "|" } from` + specifier := parent + if propertyName := specifier.PropertyName(); propertyName != nil && node != propertyName { + return nil // Don't complete in `export { "..." as "|" } from` + } + namedImportsOrExports := specifier.Parent + var moduleSpecifier *ast.Node + if namedImportsOrExports.Kind == ast.KindNamedImports { + moduleSpecifier = namedImportsOrExports.Parent.Parent + } else { + moduleSpecifier = namedImportsOrExports.Parent + } + if moduleSpecifier == nil { + return nil + } + moduleSpecifierSymbol := typeChecker.GetSymbolAtLocation(moduleSpecifier) + if moduleSpecifierSymbol == nil { + return nil + } + exports := typeChecker.GetExportsAndPropertiesOfModule(moduleSpecifierSymbol) + existing := core.NewSetFromItems(core.Map(namedImportsOrExports.Elements(), func(n *ast.Node) string { + if n.PropertyName() != nil { + return n.PropertyName().Text() + } + return n.Name().Text() + })...) + uniques := core.Filter(exports, func(e *ast.Symbol) bool { + return e.Name != ast.InternalSymbolNameDefault && !existing.Has(e.Name) + }) + return &completionsFromProperties{ + symbols: uniques, + hasIndexSignature: false, + } + default: + result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) + if result != nil { + return result + } + return fromContextualType(checker.ContextFlagsNone, node, typeChecker) } } @@ -183,7 +405,8 @@ func fromUnionableLiteralType(grandparent *ast.Node, parent *ast.Node, position func stringLiteralCompletionsForObjectLiteral( typeChecker *checker.Checker, - objectLiteralExpression *ast.ObjectLiteralExpressionNode) *completionsFromProperties { + objectLiteralExpression *ast.ObjectLiteralExpressionNode, +) *completionsFromProperties { contextualType := typeChecker.GetContextualType(objectLiteralExpression, checker.ContextFlagsNone) if contextualType == nil { return nil @@ -215,8 +438,9 @@ func getStringLiteralCompletionsFromModuleNames( file *ast.SourceFile, node *ast.LiteralExpression, program *compiler.Program, - preferences *UserPreferences) stringLiteralCompletions { - // !!! here + preferences *UserPreferences, +) stringLiteralCompletions { + // !!! needs `getModeForUsageLocationWorker` return nil } @@ -270,3 +494,47 @@ func getAlreadyUsedTypesInStringLiteralUnion(union *ast.UnionType, current *ast. func hasIndexSignature(t *checker.Type, typeChecker *checker.Checker) bool { return typeChecker.GetStringIndexType(t) != nil || typeChecker.GetNumberIndexType(t) != nil } + +// Matches +// +// require("" +// require("") +func isRequireCallArgument(node *ast.Node) bool { + return ast.IsCallExpression(node.Parent) && len(node.Parent.Arguments()) > 0 && node.Parent.Arguments()[0] == node && + ast.IsIdentifier(node.Parent.Expression()) && node.Parent.Expression().Text() == "require" +} + +func kindModifiersFromExtension(extension string) ScriptElementKindModifier { + switch extension { + case tspath.ExtensionDts: + return ScriptElementKindModifierDts + case tspath.ExtensionJs: + return ScriptElementKindModifierJs + case tspath.ExtensionJson: + return ScriptElementKindModifierJson + case tspath.ExtensionJsx: + return ScriptElementKindModifierJsx + case tspath.ExtensionTs: + return ScriptElementKindModifierTs + case tspath.ExtensionTsx: + return ScriptElementKindModifierTsx + case tspath.ExtensionDmts: + return ScriptElementKindModifierDmts + case tspath.ExtensionMjs: + return ScriptElementKindModifierMjs + case tspath.ExtensionMts: + return ScriptElementKindModifierMts + case tspath.ExtensionDcts: + return ScriptElementKindModifierDcts + case tspath.ExtensionCjs: + return ScriptElementKindModifierCjs + case tspath.ExtensionCts: + return ScriptElementKindModifierCts + case tspath.ExtensionTsBuildInfo: + panic(fmt.Sprintf("Extension %v is unsupported.", tspath.ExtensionTsBuildInfo)) + case "": + return ScriptElementKindModifierNone + default: + panic(fmt.Sprintf("Unexpected extension: %v", extension)) + } +} diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index aecffa380e..acb972b668 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -1,12 +1,14 @@ package ls import ( + "fmt" "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/jsnum" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" ) @@ -287,6 +289,14 @@ func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.Sou return &lspRange } +func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position { + lspPos, err := l.converters.ToLSPPosition(file.FileName(), core.TextPos(position)) + if err != nil { + panic(err) + } + return lspPos +} + 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) @@ -749,3 +759,76 @@ func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type } return t } + +type caseClauseTrackerState struct { + existingStrings core.Set[string] + existingNumbers core.Set[jsnum.Number] + existingBigInts core.Set[jsnum.PseudoBigInt] +} + +// string | jsnum.Number +type trackerAddValue = any + +// string | jsnum.Number | jsnum.PseudoBigInt +type trackerHasValue = any + +type caseClauseTracker interface { + addValue(value trackerAddValue) + hasValue(value trackerHasValue) bool +} + +func (c *caseClauseTrackerState) addValue(value trackerAddValue) { + switch v := value.(type) { + case string: + c.existingStrings.Add(v) + case jsnum.Number: + c.existingNumbers.Add(v) + default: + panic(fmt.Sprintf("Unsupported type: %T", v)) + } +} + +func (c *caseClauseTrackerState) hasValue(value trackerHasValue) bool { + switch v := value.(type) { + case string: + return c.existingStrings.Has(v) + case jsnum.Number: + return c.existingNumbers.Has(v) + case jsnum.PseudoBigInt: + return c.existingBigInts.Has(v) + default: + panic(fmt.Sprintf("Unsupported type: %T", v)) + } +} + +func newCaseClauseTracker(typeChecker *checker.Checker, clauses []*ast.CaseOrDefaultClauseNode) caseClauseTracker { + c := &caseClauseTrackerState{ + existingStrings: core.Set[string]{}, + existingNumbers: core.Set[jsnum.Number]{}, + existingBigInts: core.Set[jsnum.PseudoBigInt]{}, + } + for _, clause := range clauses { + if !ast.IsDefaultClause(clause) { + expression := ast.SkipParentheses(clause.Expression()) + if ast.IsLiteralExpression(expression) { + switch expression.Kind { + case ast.KindNoSubstitutionTemplateLiteral, ast.KindStringLiteral: + c.existingStrings.Add(expression.Text()) + case ast.KindNumericLiteral: + c.existingNumbers.Add(jsnum.FromString(expression.Text())) + case ast.KindBigIntLiteral: + c.existingBigInts.Add(jsnum.ParseValidBigInt(expression.Text())) + } + } else { + symbol := typeChecker.GetSymbolAtLocation(clause.Expression()) + if symbol != nil && symbol.ValueDeclaration != nil && ast.IsEnumMember(symbol.ValueDeclaration) { + enumValue := typeChecker.GetConstantValue(symbol.ValueDeclaration) + if enumValue != nil { + c.addValue(enumValue) + } + } + } + } + } + return c +} diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 6180cb88ff..2f6d58dd6d 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -197,11 +197,11 @@ func (p *Printer) getLiteralTextOfNode(node *ast.LiteralLikeNode, sourceFile *as switch { case flags&getLiteralTextFlagsJsxAttributeEscape != 0: - return "\"" + escapeJsxAttributeString(text, quoteCharDoubleQuote) + "\"" + return "\"" + escapeJsxAttributeString(text, QuoteCharDoubleQuote) + "\"" case flags&getLiteralTextFlagsNeverAsciiEscape != 0 || p.emitContext.EmitFlags(node)&EFNoAsciiEscaping != 0: - return "\"" + EscapeString(text, quoteCharDoubleQuote) + "\"" + return "\"" + EscapeString(text, QuoteCharDoubleQuote) + "\"" default: - return "\"" + escapeNonAsciiString(text, quoteCharDoubleQuote) + "\"" + return "\"" + escapeNonAsciiString(text, QuoteCharDoubleQuote) + "\"" } } } diff --git a/internal/printer/utilities.go b/internal/printer/utilities.go index 3b17373557..695e27aeb1 100644 --- a/internal/printer/utilities.go +++ b/internal/printer/utilities.go @@ -24,12 +24,12 @@ const ( getLiteralTextFlagsAllowNumericSeparator getLiteralTextFlags = 1 << 3 ) -type quoteChar rune +type QuoteChar rune const ( - quoteCharSingleQuote quoteChar = '\'' - quoteCharDoubleQuote quoteChar = '"' - quoteCharBacktick quoteChar = '`' + QuoteCharSingleQuote QuoteChar = '\'' + QuoteCharDoubleQuote QuoteChar = '"' + QuoteCharBacktick QuoteChar = '`' ) var jsxEscapedCharsMap = map[rune]string{ @@ -73,7 +73,7 @@ func encodeUtf16EscapeSequence(b *strings.Builder, charCode rune) { // Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), // but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine) // Note that this doesn't actually wrap the input in double quotes. -func escapeStringWorker(s string, quoteChar quoteChar, flags getLiteralTextFlags, b *strings.Builder) { +func escapeStringWorker(s string, quoteChar QuoteChar, flags getLiteralTextFlags, b *strings.Builder) { pos := 0 i := 0 for i < len(s) { @@ -92,13 +92,13 @@ func escapeStringWorker(s string, quoteChar quoteChar, flags getLiteralTextFlags escape = true } case '$': - if quoteChar == quoteCharBacktick && i+1 < len(s) && s[i+1] == '{' { + if quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '{' { escape = true } case rune(quoteChar), '\u2028', '\u2029', '\u0085', '\r': escape = true case '\n': - if quoteChar != quoteCharBacktick { + if quoteChar != QuoteCharBacktick { // Template strings preserve simple LF newlines, still encode CRLF (or CR). escape = true } @@ -125,7 +125,7 @@ func escapeStringWorker(s string, quoteChar quoteChar, flags getLiteralTextFlags } default: - if ch == '\r' && quoteChar == quoteCharBacktick && i+1 < len(s) && s[i+1] == '\n' { + if ch == '\r' && quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '\n' { // Template strings preserve simple LF newlines, but still must escape CRLF. Left alone, the // above cases for `\r` and `\n` would inadvertently escape CRLF as two independent characters. size++ @@ -163,21 +163,21 @@ func escapeStringWorker(s string, quoteChar quoteChar, flags getLiteralTextFlags } } -func EscapeString(s string, quoteChar quoteChar) string { +func EscapeString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsNeverAsciiEscape, &b) return b.String() } -func escapeNonAsciiString(s string, quoteChar quoteChar) string { +func escapeNonAsciiString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsNone, &b) return b.String() } -func escapeJsxAttributeString(s string, quoteChar quoteChar) string { +func escapeJsxAttributeString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsJsxAttributeEscape|getLiteralTextFlagsNeverAsciiEscape, &b) @@ -224,11 +224,11 @@ func getLiteralText(node *ast.LiteralLikeNode, sourceFile *ast.SourceFile, flags switch node.Kind { case ast.KindStringLiteral: var b strings.Builder - var quoteChar quoteChar + var quoteChar QuoteChar if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 { - quoteChar = quoteCharSingleQuote + quoteChar = QuoteCharSingleQuote } else { - quoteChar = quoteCharDoubleQuote + quoteChar = QuoteCharDoubleQuote } text := node.Text() @@ -285,7 +285,7 @@ func getLiteralText(node *ast.LiteralLikeNode, sourceFile *ast.SourceFile, flags // If rawText is set, it is expected to be valid. b.WriteString(rawText) default: - escapeStringWorker(text, quoteCharBacktick, flags, &b) + escapeStringWorker(text, QuoteCharBacktick, flags, &b) } // Write trailing quote character diff --git a/internal/printer/utilities_test.go b/internal/printer/utilities_test.go index cc12ba64b1..cbe034f22d 100644 --- a/internal/printer/utilities_test.go +++ b/internal/printer/utilities_test.go @@ -13,18 +13,18 @@ func TestEscapeString(t *testing.T) { t.Parallel() data := []struct { s string - quoteChar quoteChar + quoteChar QuoteChar expected string }{ - {s: "", quoteChar: quoteCharDoubleQuote, expected: ``}, - {s: "abc", quoteChar: quoteCharDoubleQuote, expected: `abc`}, - {s: "ab\"c", quoteChar: quoteCharDoubleQuote, expected: `ab\"c`}, - {s: "ab\tc", quoteChar: quoteCharDoubleQuote, expected: `ab\tc`}, - {s: "ab\nc", quoteChar: quoteCharDoubleQuote, expected: `ab\nc`}, - {s: "ab'c", quoteChar: quoteCharDoubleQuote, expected: `ab'c`}, - {s: "ab'c", quoteChar: quoteCharSingleQuote, expected: `ab\'c`}, - {s: "ab\"c", quoteChar: quoteCharSingleQuote, expected: `ab"c`}, - {s: "ab`c", quoteChar: quoteCharBacktick, expected: "ab\\`c"}, + {s: "", quoteChar: QuoteCharDoubleQuote, expected: ``}, + {s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`}, + {s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`}, + {s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`}, + {s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`}, + {s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`}, + {s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`}, + {s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`}, + {s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"}, } for i, rec := range data { t.Run(fmt.Sprintf("[%d] escapeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) { @@ -39,20 +39,20 @@ func TestEscapeNonAsciiString(t *testing.T) { t.Parallel() data := []struct { s string - quoteChar quoteChar + quoteChar QuoteChar expected string }{ - {s: "", quoteChar: quoteCharDoubleQuote, expected: ``}, - {s: "abc", quoteChar: quoteCharDoubleQuote, expected: `abc`}, - {s: "ab\"c", quoteChar: quoteCharDoubleQuote, expected: `ab\"c`}, - {s: "ab\tc", quoteChar: quoteCharDoubleQuote, expected: `ab\tc`}, - {s: "ab\nc", quoteChar: quoteCharDoubleQuote, expected: `ab\nc`}, - {s: "ab'c", quoteChar: quoteCharDoubleQuote, expected: `ab'c`}, - {s: "ab'c", quoteChar: quoteCharSingleQuote, expected: `ab\'c`}, - {s: "ab\"c", quoteChar: quoteCharSingleQuote, expected: `ab"c`}, - {s: "ab`c", quoteChar: quoteCharBacktick, expected: "ab\\`c"}, - {s: "ab\u008fc", quoteChar: quoteCharDoubleQuote, expected: `ab\u008Fc`}, - {s: "𝟘𝟙", quoteChar: quoteCharDoubleQuote, expected: `\uD835\uDFD8\uD835\uDFD9`}, + {s: "", quoteChar: QuoteCharDoubleQuote, expected: ``}, + {s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`}, + {s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`}, + {s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`}, + {s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`}, + {s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`}, + {s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`}, + {s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`}, + {s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"}, + {s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: `ab\u008Fc`}, + {s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: `\uD835\uDFD8\uD835\uDFD9`}, } for i, rec := range data { t.Run(fmt.Sprintf("[%d] escapeNonAsciiString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) { @@ -67,19 +67,19 @@ func TestEscapeJsxAttributeString(t *testing.T) { t.Parallel() data := []struct { s string - quoteChar quoteChar + quoteChar QuoteChar expected string }{ - {s: "", quoteChar: quoteCharDoubleQuote, expected: ""}, - {s: "abc", quoteChar: quoteCharDoubleQuote, expected: "abc"}, - {s: "ab\"c", quoteChar: quoteCharDoubleQuote, expected: "ab"c"}, - {s: "ab\tc", quoteChar: quoteCharDoubleQuote, expected: "ab c"}, - {s: "ab\nc", quoteChar: quoteCharDoubleQuote, expected: "ab c"}, - {s: "ab'c", quoteChar: quoteCharDoubleQuote, expected: "ab'c"}, - {s: "ab'c", quoteChar: quoteCharSingleQuote, expected: "ab'c"}, - {s: "ab\"c", quoteChar: quoteCharSingleQuote, expected: "ab\"c"}, - {s: "ab\u008fc", quoteChar: quoteCharDoubleQuote, expected: "ab\u008Fc"}, - {s: "𝟘𝟙", quoteChar: quoteCharDoubleQuote, expected: "𝟘𝟙"}, + {s: "", quoteChar: QuoteCharDoubleQuote, expected: ""}, + {s: "abc", quoteChar: QuoteCharDoubleQuote, expected: "abc"}, + {s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: "ab"c"}, + {s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: "ab c"}, + {s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: "ab c"}, + {s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: "ab'c"}, + {s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: "ab'c"}, + {s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: "ab\"c"}, + {s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: "ab\u008Fc"}, + {s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: "𝟘𝟙"}, } for i, rec := range data { t.Run(fmt.Sprintf("[%d] escapeJsxAttributeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) { From 5b05a943c716e30e5305bea54e576d70a302f32b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 16 May 2025 17:31:42 -0700 Subject: [PATCH 03/10] WIP: switch completions --- internal/ls/completions.go | 21 ++- internal/ls/completions_test.go | 276 ++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 4 deletions(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index a83247b956..62ef755b5b 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -256,7 +256,8 @@ const ( // true otherwise. type uniqueNamesMap = map[string]bool -type literalValue any // string | jsnum.Number | PseudoBigInt +// string | jsnum.Number | PseudoBigInt +type literalValue any type globalsSearch int @@ -1485,10 +1486,10 @@ func (l *LanguageService) completionInfoFromData( clientOptions *lsproto.CompletionClientCapabilities, ) *lsproto.CompletionList { keywordFilters := data.keywordFilters - symbols := data.symbols isNewIdentifierLocation := data.isNewIdentifierLocation contextToken := data.contextToken literals := data.literals + typeChecker := program.GetTypeChecker() // Verify if the file is JSX language variant if ast.GetLanguageVariant(file.ScriptKind) == core.LanguageVariantJSX { @@ -1504,11 +1505,23 @@ func (l *LanguageService) completionInfoFromData( if caseClause != nil && (contextToken.Kind == ast.KindCaseKeyword || ast.IsNodeDescendantOf(contextToken, caseClause.Expression())) { - // !!! switch completions + tracker := newCaseClauseTracker(typeChecker, caseClause.Parent.AsCaseBlock().Clauses.Nodes) + literals = core.Filter(literals, func(literal literalValue) bool { + return !tracker.hasValue(literal) + }) + data.symbols = core.Filter(data.symbols, func(symbol *ast.Symbol) bool { + if symbol.ValueDeclaration != nil && ast.IsEnumMember(symbol.ValueDeclaration) { + value := typeChecker.GetConstantValue(symbol.ValueDeclaration) + if value != nil && tracker.hasValue(value) { + return false + } + } + return true + }) } isChecked := isCheckedFile(file, compilerOptions) - if isChecked && !isNewIdentifierLocation && len(symbols) == 0 && keywordFilters == KeywordCompletionFiltersNone { + if isChecked && !isNewIdentifierLocation && len(data.symbols) == 0 && keywordFilters == KeywordCompletionFiltersNone { return nil } diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index 3b9b1edc0d..678553df01 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -54,6 +54,9 @@ func TestCompletions(t *testing.T) { variableKind := ptrTo(lsproto.CompletionItemKindVariable) classKind := ptrTo(lsproto.CompletionItemKindClass) keywordKind := ptrTo(lsproto.CompletionItemKindKeyword) + propertyKind := ptrTo(lsproto.CompletionItemKindProperty) + constantKind := ptrTo(lsproto.CompletionItemKindConstant) + enumMemberKind := ptrTo(lsproto.CompletionItemKindEnumMember) stringMembers := []*lsproto.CompletionItem{ {Label: "charAt", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, @@ -1528,6 +1531,279 @@ function fn3() { }, }, }, + { + name: "completionListWithLabel", + files: map[string]string{ + defaultMainFileName: `label: while (true) { + break /*1*/ + continue /*2*/ + testlabel: while (true) { + break /*3*/ + continue /*4*/ + break tes/*5*/ + continue tes/*6*/ + } + break /*7*/ + break; /*8*/ +}`, + }, + expectedResult: map[string]*testCaseResult{ + "1": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "2": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "7": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "3": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "4": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "5": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "6": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + { + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + }, + }, + }, + }, + "8": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{}, + }, + isIncludes: true, + excludes: []string{"label"}, + }, + }, + }, + { + name: "completionForStringLiteral", + files: map[string]string{ + defaultMainFileName: `type Options = "Option 1" | "Option 2" | "Option 3"; +var x: Options = "/*1*/Option 3";`, + }, + expectedResult: map[string]*testCaseResult{ + "1": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "Option 1", + Kind: constantKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "Option 1", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 1, Character: 18}, + End: lsproto.Position{Line: 1, Character: 26}, + }, + }, + }, + }, + { + Label: "Option 2", + Kind: constantKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "Option 2", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 1, Character: 18}, + End: lsproto.Position{Line: 1, Character: 26}, + }, + }, + }, + }, + { + Label: "Option 3", + Kind: constantKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "Option 3", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 1, Character: 18}, + End: lsproto.Position{Line: 1, Character: 26}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "switchCompletions", + files: map[string]string{ + defaultMainFileName: `enum E { A, B } +declare const e: E; +switch (e) { + case E.A: + return 0; + case E./*1*/ +} +declare const f: 1 | 2 | 3; +switch (f) { + case 1: + return 1; + case /*2*/ +} +declare const f2: 'foo' | 'bar' | 'baz'; +switch (f2) { + case 'bar': + return 1; + case '/*3*/' +} +// repro from #52874 +declare let x: "foo" | "bar"; +switch (x) { + case ('/*4*/') +}`, + }, + expectedResult: map[string]*testCaseResult{ + "1": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "B", + Kind: enumMemberKind, + SortText: sortTextLocationPriority, + InsertTextFormat: insertTextFormatPlainText, + FilterText: ptrTo(".B"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + InsertReplaceEdit: &lsproto.InsertReplaceEdit{ + NewText: "B", + Insert: lsproto.Range{ + Start: lsproto.Position{Line: 5, Character: 11}, + End: lsproto.Position{Line: 5, Character: 11}, + }, + Replace: lsproto.Range{ + Start: lsproto.Position{Line: 5, Character: 11}, + End: lsproto.Position{Line: 5, Character: 11}, + }, + }, + }, + }, + }, + }, + isIncludes: true, + excludes: []string{"A"}, + }, + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { From b4a41f3fadf07ebfd5487fed16d0710d272dd7ce Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 20 May 2025 12:19:14 -0700 Subject: [PATCH 04/10] switch completions --- internal/ls/completions.go | 3 +- internal/ls/completions_test.go | 707 +++++++++++++++++--------------- 2 files changed, 386 insertions(+), 324 deletions(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 62ef755b5b..d42a1e3075 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -4108,11 +4108,10 @@ func (l *LanguageService) createLSPCompletionItem( // !!! adjust label like vscode does } + // Client assumes plain text by default. var insertTextFormat *lsproto.InsertTextFormat if isSnippet { insertTextFormat = ptrTo(lsproto.InsertTextFormatSnippet) - } else { - insertTextFormat = ptrTo(lsproto.InsertTextFormatPlainText) } return &lsproto.CompletionItem{ diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index 678553df01..e9478d8fc8 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -43,7 +43,6 @@ func TestCompletions(t *testing.T) { itemDefaults := &lsproto.CompletionItemDefaults{ CommitCharacters: &defaultCommitCharacters, } - insertTextFormatPlainText := ptrTo(lsproto.InsertTextFormatPlainText) sortTextLocationPriority := ptrTo(string(ls.SortTextLocationPriority)) sortTextLocalDeclarationPriority := ptrTo(string(ls.SortTextLocalDeclarationPriority)) sortTextDeprecatedLocationPriority := ptrTo(string(ls.DeprecateSortText(ls.SortTextLocationPriority))) @@ -59,52 +58,52 @@ func TestCompletions(t *testing.T) { enumMemberKind := ptrTo(lsproto.CompletionItemKindEnumMember) stringMembers := []*lsproto.CompletionItem{ - {Label: "charAt", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "charCodeAt", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "concat", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "indexOf", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "lastIndexOf", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "length", Kind: fieldKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "localeCompare", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "match", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "replace", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "search", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "slice", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "split", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "substring", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toLocaleLowerCase", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toLocaleUpperCase", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toLowerCase", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toString", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toUpperCase", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "trim", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "valueOf", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "substr", Kind: methodKind, SortText: sortTextDeprecatedLocationPriority, InsertTextFormat: insertTextFormatPlainText}, + {Label: "charAt", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "charCodeAt", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "concat", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "indexOf", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "lastIndexOf", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "length", Kind: fieldKind, SortText: sortTextLocationPriority}, + {Label: "localeCompare", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "match", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "replace", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "search", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "slice", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "split", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "substring", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toLocaleLowerCase", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toLocaleUpperCase", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toLowerCase", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toString", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toUpperCase", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "trim", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "valueOf", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "substr", Kind: methodKind, SortText: sortTextDeprecatedLocationPriority}, } arrayMembers := []*lsproto.CompletionItem{ - {Label: "concat", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "every", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "filter", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "forEach", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "indexOf", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "join", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "lastIndexOf", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "length", Kind: fieldKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "map", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "pop", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "push", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "reduce", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "reduceRight", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "reverse", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "shift", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "slice", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "some", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "sort", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "splice", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toLocaleString", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "toString", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, - {Label: "unshift", Kind: methodKind, SortText: sortTextLocationPriority, InsertTextFormat: insertTextFormatPlainText}, + {Label: "concat", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "every", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "filter", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "forEach", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "indexOf", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "join", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "lastIndexOf", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "length", Kind: fieldKind, SortText: sortTextLocationPriority}, + {Label: "map", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "pop", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "push", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "reduce", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "reduceRight", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "reverse", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "shift", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "slice", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "some", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "sort", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "splice", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toLocaleString", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "toString", Kind: methodKind, SortText: sortTextLocationPriority}, + {Label: "unshift", Kind: methodKind, SortText: sortTextLocationPriority}, } testCases := []testCase{ @@ -126,11 +125,11 @@ p./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".x"), - InsertTextFormat: insertTextFormatPlainText, + Label: "x", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".x"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "x", @@ -146,11 +145,11 @@ p./*a*/`, }, }, { - Label: "y", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".y"), - InsertTextFormat: insertTextFormatPlainText, + Label: "y", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".y"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "y", @@ -189,12 +188,12 @@ p./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".?.x"), - InsertText: ptrTo("?.x"), - InsertTextFormat: insertTextFormatPlainText, + Label: "x", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".?.x"), + InsertText: ptrTo("?.x"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "?.x", @@ -206,12 +205,12 @@ p./*a*/`, }, }, { - Label: "y", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".?.y"), - InsertText: ptrTo("?.y"), - InsertTextFormat: insertTextFormatPlainText, + Label: "y", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".?.y"), + InsertText: ptrTo("?.y"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "?.y", @@ -241,11 +240,10 @@ x./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "foo", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".foo"), - InsertTextFormat: ptrTo(lsproto.InsertTextFormatPlainText), + Label: "foo", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".foo"), TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "foo", @@ -281,11 +279,11 @@ var t = new n(0, 1, '');t./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".x"), - InsertTextFormat: insertTextFormatPlainText, + Label: "x", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".x"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "x", @@ -301,11 +299,11 @@ var t = new n(0, 1, '');t./*a*/`, }, }, { - Label: "y", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".y"), - InsertTextFormat: insertTextFormatPlainText, + Label: "y", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".y"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "y", @@ -357,11 +355,11 @@ D./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ // !!! `funcionMembersPlus` { - Label: "bar", - Kind: methodKind, - SortText: sortTextLocalDeclarationPriority, - FilterText: ptrTo(".bar"), - InsertTextFormat: insertTextFormatPlainText, + Label: "bar", + Kind: methodKind, + SortText: sortTextLocalDeclarationPriority, + FilterText: ptrTo(".bar"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "bar", @@ -377,11 +375,11 @@ D./*a*/`, }, }, { - Label: "bar2", - Kind: methodKind, - SortText: sortTextLocalDeclarationPriority, - FilterText: ptrTo(".bar2"), - InsertTextFormat: insertTextFormatPlainText, + Label: "bar2", + Kind: methodKind, + SortText: sortTextLocalDeclarationPriority, + FilterText: ptrTo(".bar2"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "bar2", @@ -397,11 +395,11 @@ D./*a*/`, }, }, { - Label: "apply", - Kind: methodKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".apply"), - InsertTextFormat: insertTextFormatPlainText, + Label: "apply", + Kind: methodKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".apply"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "apply", @@ -417,11 +415,11 @@ D./*a*/`, }, }, { - Label: "arguments", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".arguments"), - InsertTextFormat: insertTextFormatPlainText, + Label: "arguments", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".arguments"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "arguments", @@ -437,11 +435,11 @@ D./*a*/`, }, }, { - Label: "baz", - Kind: functionKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".baz"), - InsertTextFormat: insertTextFormatPlainText, + Label: "baz", + Kind: functionKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".baz"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "baz", @@ -457,11 +455,11 @@ D./*a*/`, }, }, { - Label: "bind", - Kind: methodKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".bind"), - InsertTextFormat: insertTextFormatPlainText, + Label: "bind", + Kind: methodKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".bind"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "bind", @@ -477,11 +475,11 @@ D./*a*/`, }, }, { - Label: "call", - Kind: methodKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".call"), - InsertTextFormat: insertTextFormatPlainText, + Label: "call", + Kind: methodKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".call"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "call", @@ -497,11 +495,11 @@ D./*a*/`, }, }, { - Label: "caller", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".caller"), - InsertTextFormat: insertTextFormatPlainText, + Label: "caller", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".caller"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "caller", @@ -517,11 +515,11 @@ D./*a*/`, }, }, { - Label: "length", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".length"), - InsertTextFormat: insertTextFormatPlainText, + Label: "length", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".length"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "length", @@ -537,11 +535,11 @@ D./*a*/`, }, }, { - Label: "prototype", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".prototype"), - InsertTextFormat: insertTextFormatPlainText, + Label: "prototype", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".prototype"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "prototype", @@ -557,11 +555,11 @@ D./*a*/`, }, }, { - Label: "toString", - Kind: methodKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".toString"), - InsertTextFormat: insertTextFormatPlainText, + Label: "toString", + Kind: methodKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".toString"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "toString", @@ -577,11 +575,11 @@ D./*a*/`, }, }, { - Label: "x", - Kind: variableKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".x"), - InsertTextFormat: insertTextFormatPlainText, + Label: "x", + Kind: variableKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".x"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "x", @@ -620,11 +618,11 @@ D./*a*/`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "a", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".a"), - InsertTextFormat: insertTextFormatPlainText, + Label: "a", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".a"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "a", @@ -640,11 +638,11 @@ D./*a*/`, }, }, { - Label: "b", - Kind: methodKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo(".b"), - InsertTextFormat: insertTextFormatPlainText, + Label: "b", + Kind: methodKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo(".b"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "b", @@ -710,12 +708,12 @@ x./**/;`, ItemDefaults: itemDefaults, Items: append([]*lsproto.CompletionItem{ { - Label: "0", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertText: ptrTo("[0]"), - InsertTextFormat: insertTextFormatPlainText, - FilterText: ptrTo(".[0]"), + Label: "0", + Kind: fieldKind, + SortText: sortTextLocationPriority, + InsertText: ptrTo("[0]"), + + FilterText: ptrTo(".[0]"), TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "[0]", @@ -727,12 +725,12 @@ x./**/;`, }, }, { - Label: "1", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertText: ptrTo("[1]"), - InsertTextFormat: insertTextFormatPlainText, - FilterText: ptrTo(".[1]"), + Label: "1", + Kind: fieldKind, + SortText: sortTextLocationPriority, + InsertText: ptrTo("[1]"), + + FilterText: ptrTo(".[1]"), TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "[1]", @@ -779,10 +777,9 @@ namespace c5b { export var y = 2; } // should be ok ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "c5b", - Kind: classKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "c5b", + Kind: classKind, + SortText: sortTextLocationPriority, }, }, }, @@ -818,16 +815,14 @@ let x: Foo = { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x1", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x1", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "x2", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x2", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -838,16 +833,14 @@ let x: Foo = { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x1", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x1", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "x2", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x2", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -858,16 +851,14 @@ let x: Foo = { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "x1", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x1", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "x2", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "x2", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -897,10 +888,9 @@ var foobar: Bar<{ one: string, /**/`, }, Items: []*lsproto.CompletionItem{ { - Label: "two", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "two", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -925,22 +915,19 @@ export = Foo;`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "prop1", - Kind: methodKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "prop1", + Kind: methodKind, + SortText: sortTextLocationPriority, }, { - Label: "prop2", - Kind: methodKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "prop2", + Kind: methodKind, + SortText: sortTextLocationPriority, }, { - Label: "prototype", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "prototype", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { Label: "type", @@ -976,16 +963,14 @@ import * as t4 from "./a" with { type: /*4*/ };`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "resolution-mode", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "resolution-mode", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "type", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "type", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1008,10 +993,9 @@ if (!!true) { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "topLevel", - Kind: functionKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "topLevel", + Kind: functionKind, + SortText: sortTextLocationPriority, }, { Label: "type", @@ -1104,12 +1088,12 @@ declare namespace React { }, Items: []*lsproto.CompletionItem{ { - Label: "contextType?", - Kind: fieldKind, - SortText: sortTextLocationPriority, - FilterText: ptrTo("contextType"), - InsertText: ptrTo("contextType"), - InsertTextFormat: insertTextFormatPlainText, + Label: "contextType?", + Kind: fieldKind, + SortText: sortTextLocationPriority, + FilterText: ptrTo("contextType"), + InsertText: ptrTo("contextType"), + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "contextType", @@ -1224,16 +1208,14 @@ class Foo { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "aria-label", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "aria-label", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "foo", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "foo", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1244,16 +1226,14 @@ class Foo { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "aria-label", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "aria-label", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, { - Label: "foo", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "foo", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1279,10 +1259,10 @@ const bar: { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "foo", - Kind: variableKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "foo", + Kind: variableKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "foo", @@ -1454,10 +1434,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "button", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "button", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1469,10 +1448,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "button", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "button", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1484,10 +1462,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "button", - Kind: fieldKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "button", + Kind: fieldKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1508,10 +1485,10 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "div>", - Kind: classKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "div>", + Kind: classKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "div>", @@ -1554,10 +1531,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1568,10 +1544,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1582,10 +1557,9 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1596,16 +1570,14 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "testlabel", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1616,16 +1588,14 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "testlabel", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1636,16 +1606,14 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "testlabel", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1656,16 +1624,14 @@ function fn3() { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "testlabel", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "testlabel", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, { - Label: "label", - Kind: propertyKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "label", + Kind: propertyKind, + SortText: sortTextLocationPriority, }, }, }, @@ -1694,10 +1660,10 @@ var x: Options = "/*1*/Option 3";`, ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "Option 1", - Kind: constantKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "Option 1", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "Option 1", @@ -1709,10 +1675,10 @@ var x: Options = "/*1*/Option 3";`, }, }, { - Label: "Option 2", - Kind: constantKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "Option 2", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "Option 2", @@ -1724,10 +1690,10 @@ var x: Options = "/*1*/Option 3";`, }, }, { - Label: "Option 3", - Kind: constantKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "Option 3", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ TextEdit: &lsproto.TextEdit{ NewText: "Option 3", @@ -1778,11 +1744,11 @@ switch (x) { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "B", - Kind: enumMemberKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, - FilterText: ptrTo(".B"), + Label: "B", + Kind: enumMemberKind, + SortText: sortTextLocationPriority, + + FilterText: ptrTo(".B"), TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "B", @@ -1802,6 +1768,103 @@ switch (x) { isIncludes: true, excludes: []string{"A"}, }, + "2": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "2", + Kind: constantKind, + SortText: sortTextLocationPriority, + CommitCharacters: &[]string{}, + }, + { + Label: "3", + Kind: constantKind, + SortText: sortTextLocationPriority, + CommitCharacters: &[]string{}, + }, + }, + }, + isIncludes: true, + excludes: []string{"1"}, + }, + "3": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "foo", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "foo", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 17, Character: 10}, + End: lsproto.Position{Line: 17, Character: 10}, + }, + }, + }, + }, + { + Label: "baz", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "baz", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 17, Character: 10}, + End: lsproto.Position{Line: 17, Character: 10}, + }, + }, + }, + }, + }, + }, + isIncludes: true, + excludes: []string{"bar"}, + }, + "4": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "foo", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "foo", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 22, Character: 11}, + End: lsproto.Position{Line: 22, Character: 11}, + }, + }, + }, + }, + { + Label: "bar", + Kind: constantKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "bar", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 22, Character: 11}, + End: lsproto.Position{Line: 22, Character: 11}, + }, + }, + }, + }, + }, + }, + isIncludes: true, + }, }, }, } From 4daaea46d98fbec899dffcd01ddfe1639e4bfced Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 20 May 2025 18:08:00 -0700 Subject: [PATCH 05/10] update ctx --- internal/ls/completions.go | 4 +++- internal/ls/completions_test.go | 7 +++---- internal/ls/string_completions.go | 10 +++++++++- internal/ls/utilities.go | 6 +----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index d42a1e3075..b8370072ca 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -296,6 +296,7 @@ func (l *LanguageService) getCompletionsAtPosition( // !!! see if incomplete completion list and continue or clean stringCompletions := l.getStringLiteralCompletions( + ctx, file, position, previousToken, @@ -1489,7 +1490,8 @@ func (l *LanguageService) completionInfoFromData( isNewIdentifierLocation := data.isNewIdentifierLocation contextToken := data.contextToken literals := data.literals - typeChecker := program.GetTypeChecker() + typeChecker, done := program.GetTypeChecker(ctx) + defer done() // Verify if the file is JSX language variant if ast.GetLanguageVariant(file.ScriptKind) == core.LanguageVariantJSX { diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index e9478d8fc8..5f86c4abbd 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -1377,10 +1377,9 @@ export function isAnyDirectorySeparator(charCode: number): boolean { ItemDefaults: itemDefaults, Items: []*lsproto.CompletionItem{ { - Label: "CharacterCodes", - Kind: variableKind, - SortText: sortTextLocationPriority, - InsertTextFormat: insertTextFormatPlainText, + Label: "CharacterCodes", + Kind: variableKind, + SortText: sortTextLocationPriority, TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ InsertReplaceEdit: &lsproto.InsertReplaceEdit{ NewText: "CharacterCodes", diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 6101182ae5..b990f3be80 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -1,6 +1,7 @@ package ls import ( + "context" "fmt" "slices" "strings" @@ -38,6 +39,7 @@ type pathCompletion struct { type stringLiteralCompletions = any func (l *LanguageService) getStringLiteralCompletions( + ctx context.Context, file *ast.SourceFile, position int, contextToken *ast.Node, @@ -52,12 +54,14 @@ func (l *LanguageService) getStringLiteralCompletions( return nil } entries := l.getStringLiteralCompletionEntries( + ctx, file, contextToken, position, program, preferences) return l.convertStringLiteralCompletions( + ctx, entries, contextToken, file, @@ -72,6 +76,7 @@ func (l *LanguageService) getStringLiteralCompletions( } func (l *LanguageService) convertStringLiteralCompletions( + ctx context.Context, completion stringLiteralCompletions, contextToken *ast.StringLiteralLike, file *ast.SourceFile, @@ -98,6 +103,7 @@ func (l *LanguageService) convertStringLiteralCompletions( contextToken: contextToken, } _, items := l.getCompletionEntriesFromSymbols( + ctx, data, optionalReplacementRange, contextToken, /*replacementToken*/ @@ -200,13 +206,15 @@ func (l *LanguageService) convertPathCompletions( } func (l *LanguageService) getStringLiteralCompletionEntries( + ctx context.Context, file *ast.SourceFile, node *ast.StringLiteralLike, position int, program *compiler.Program, preferences *UserPreferences, ) stringLiteralCompletions { - typeChecker := program.GetTypeChecker() + typeChecker, done := program.GetTypeChecker(ctx) + done() parent := walkUpParentheses(node.Parent) switch parent.Kind { case ast.KindLiteralType: diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index acb972b668..2b0b301fcd 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -290,11 +290,7 @@ func (l *LanguageService) createLspRangeFromBounds(start, end int, file *ast.Sou } func (l *LanguageService) createLspPosition(position int, file *ast.SourceFile) lsproto.Position { - lspPos, err := l.converters.ToLSPPosition(file.FileName(), core.TextPos(position)) - if err != nil { - panic(err) - } - return lspPos + return l.converters.PositionToLineAndCharacter(file, core.TextPos(position)) } func quote(file *ast.SourceFile, preferences *UserPreferences, text string) string { From 6d08ff06a2ce0f130e0a786a3573b438a8bff159 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 20 May 2025 18:12:44 -0700 Subject: [PATCH 06/10] add checker method to exports.go --- internal/checker/checker.go | 36 +++++++++++++++++------------------ internal/checker/exports.go | 4 ++++ internal/checker/flow.go | 2 +- internal/checker/inference.go | 6 +++--- internal/checker/relater.go | 10 +++++----- internal/ls/utilities.go | 2 +- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 515f8b9092..87168d9b0b 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -2396,7 +2396,7 @@ func (c *Checker) checkTypeParameter(node *ast.Node) { c.checkSourceElement(tpNode.DefaultType) typeParameter := c.getDeclaredTypeOfTypeParameter(c.getSymbolOfDeclaration(node)) // Resolve base constraint to reveal circularity errors - c.GetBaseConstraintOfType(typeParameter) + c.getBaseConstraintOfType(typeParameter) if c.getResolvedTypeParameterDefault(typeParameter) == c.circularConstraintType { c.error(tpNode.DefaultType, diagnostics.Type_parameter_0_has_a_circular_default, c.TypeToString(typeParameter)) } @@ -7593,7 +7593,7 @@ func (c *Checker) isTemplateLiteralContext(node *ast.Node) bool { } func (c *Checker) isTemplateLiteralContextualType(t *Type) bool { - return t.flags&(TypeFlagsStringLiteral|TypeFlagsTemplateLiteral) != 0 || t.flags&TypeFlagsInstantiableNonPrimitive != 0 && c.maybeTypeOfKind(core.OrElse(c.GetBaseConstraintOfType(t), c.unknownType), TypeFlagsStringLike) + return t.flags&(TypeFlagsStringLiteral|TypeFlagsTemplateLiteral) != 0 || t.flags&TypeFlagsInstantiableNonPrimitive != 0 && c.maybeTypeOfKind(core.OrElse(c.getBaseConstraintOfType(t), c.unknownType), TypeFlagsStringLike) } func (c *Checker) checkRegularExpressionLiteral(node *ast.Node) *Type { @@ -10087,7 +10087,7 @@ func (c *Checker) getInstantiationExpressionType(exprType *Type, node *ast.Node) return result } } else if t.flags&TypeFlagsInstantiableNonPrimitive != 0 { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) if constraint != nil { instantiated := getInstantiatedTypePart(constraint) if instantiated != constraint { @@ -11245,7 +11245,7 @@ func (c *Checker) checkPropertyAccessibilityAtLocation(location *ast.Node, isSup if containingType.AsTypeParameter().isThisType { containingType = c.getConstraintOfTypeParameter(containingType) } else { - containingType = c.GetBaseConstraintOfType(containingType) + containingType = c.getBaseConstraintOfType(containingType) } } if containingType == nil || !c.hasBaseType(containingType, enclosingClass) { @@ -15669,7 +15669,7 @@ func (c *Checker) isConstructorType(t *Type) bool { return true } if t.flags&TypeFlagsTypeVariable != 0 { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) return constraint != nil && c.isMixinConstructorType(constraint) } return false @@ -15707,7 +15707,7 @@ func (c *Checker) getConstraintOfType(t *Type) *Type { case t.flags&TypeFlagsConditional != 0: return c.getConstraintOfConditionalType(t) } - return c.GetBaseConstraintOfType(t) + return c.getBaseConstraintOfType(t) } func (c *Checker) getConstraintOfTypeParameter(typeParameter *Type) *Type { @@ -18063,7 +18063,7 @@ func (c *Checker) reportCircularBaseType(node *ast.Node, t *Type) { // A valid base type is `any`, an object type or intersection of object types. func (c *Checker) isValidBaseType(t *Type) bool { if t.flags&TypeFlagsTypeParameter != 0 { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) if constraint != nil { return c.isValidBaseType(constraint) } @@ -20190,7 +20190,7 @@ func (c *Checker) isMappedTypeGenericIndexedAccess(t *Type) bool { func (c *Checker) getApparentType(t *Type) *Type { originalType := t if t.flags&TypeFlagsInstantiable != 0 { - t = c.GetBaseConstraintOfType(t) + t = c.getBaseConstraintOfType(t) if t == nil { t = c.unknownType } @@ -20245,7 +20245,7 @@ func (c *Checker) getResolvedApparentTypeOfMappedType(t *Type) *Type { if c.isGenericMappedType(modifiersType) { baseConstraint = c.getApparentTypeOfMappedType(modifiersType) } else { - baseConstraint = c.GetBaseConstraintOfType(modifiersType) + baseConstraint = c.getBaseConstraintOfType(modifiersType) } if baseConstraint != nil && everyType(baseConstraint, func(t *Type) bool { return c.isArrayOrTupleType(t) || c.isArrayOrTupleOrIntersection(t) }) { return c.instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, t.AsMappedType().mapper)) @@ -23826,7 +23826,7 @@ func (c *Checker) isLiteralOfContextualType(candidateType *Type, contextualType // If the contextual type is a type variable constrained to a primitive type, consider // this a literal context for literals of that primitive type. For example, given a // type parameter 'T extends string', infer string literal types for T. - constraint := c.GetBaseConstraintOfType(contextualType) + constraint := c.getBaseConstraintOfType(contextualType) if constraint == nil { constraint = c.unknownType } @@ -24191,7 +24191,7 @@ func (c *Checker) removeConstrainedTypeVariables(types []*Type) []*Type { } // If every constituent in the type variable's constraint is covered by an intersection of the type // variable and that constituent, remove those intersections and substitute the type variable. - constraint := c.GetBaseConstraintOfType(typeVariable) + constraint := c.getBaseConstraintOfType(typeVariable) if everyType(constraint, func(t *Type) bool { return containsType(primitives, t) }) { i := len(types) for i > 0 { @@ -24414,7 +24414,7 @@ func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, if typeVariable.flags&TypeFlagsTypeVariable != 0 && (primitiveType.flags&(TypeFlagsPrimitive|TypeFlagsNonPrimitive) != 0 && !c.isGenericStringLikeType(primitiveType) || includes&TypeFlagsIncludesEmptyObject != 0) { // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}. - constraint := c.GetBaseConstraintOfType(typeVariable) + constraint := c.getBaseConstraintOfType(typeVariable) // Check that T's constraint is similarly composed of primitive types, the object type, or {}. if constraint != nil && everyType(constraint, c.isPrimitiveOrObjectOrEmptyType) { // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`, @@ -25668,14 +25668,14 @@ func (c *Checker) getOrCreateSubstitutionType(baseType *Type, constraint *Type) } func (c *Checker) getBaseConstraintOrType(t *Type) *Type { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) if constraint != nil { return constraint } return t } -func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { +func (c *Checker) getBaseConstraintOfType(t *Type) *Type { if t.flags&(TypeFlagsInstantiableNonPrimitive|TypeFlagsUnionOrIntersection|TypeFlagsTemplateLiteral|TypeFlagsStringMapping) != 0 || c.isGenericTupleType(t) { constraint := c.getResolvedBaseConstraint(t, nil) if constraint != c.noConstraintType && constraint != c.circularConstraintType { @@ -27185,7 +27185,7 @@ func (c *Checker) substituteIndexedMappedType(objectType *Type, index *Type) *Ty // Return true if an indexed access with the given object and index types could access an optional property. func (c *Checker) couldAccessOptionalProperty(objectType *Type, indexType *Type) bool { - indexConstraint := c.GetBaseConstraintOfType(indexType) + indexConstraint := c.getBaseConstraintOfType(indexType) return indexConstraint != nil && core.Some(c.getPropertiesOfType(objectType), func(p *ast.Symbol) bool { return p.Flags&ast.SymbolFlagsOptional != 0 && c.isTypeAssignableTo(c.getLiteralTypeFromProperty(p, TypeFlagsStringOrNumberLiteralOrUnique, false), indexConstraint) }) @@ -28810,7 +28810,7 @@ func (c *Checker) hasTypeFacts(t *Type, mask TypeFacts) bool { func (c *Checker) getTypeFactsWorker(t *Type, callerOnlyNeeds TypeFacts) TypeFacts { if t.flags&(TypeFlagsIntersection|TypeFlagsInstantiable) != 0 { - t = c.GetBaseConstraintOfType(t) + t = c.getBaseConstraintOfType(t) if t == nil { t = c.unknownType } @@ -29225,7 +29225,7 @@ func (c *Checker) isAwaitedTypeNeeded(t *Type) bool { } // We only need `Awaited` if `T` contains possibly non-primitive types. if c.isGenericObjectType(t) { - baseConstraint := c.GetBaseConstraintOfType(t) + baseConstraint := c.getBaseConstraintOfType(t) // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, // or is promise-like. if baseConstraint != nil { @@ -29389,7 +29389,7 @@ func (c *Checker) getNonUndefinedType(t *Type) *Type { func (c *Checker) isGenericTypeWithUndefinedConstraint(t *Type) bool { if t.flags&TypeFlagsInstantiable != 0 { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) if constraint != nil { return c.maybeTypeOfKind(constraint, TypeFlagsUndefined) } diff --git a/internal/checker/exports.go b/internal/checker/exports.go index d04b7a97cc..4766066d2f 100644 --- a/internal/checker/exports.go +++ b/internal/checker/exports.go @@ -52,3 +52,7 @@ func GetDeclarationModifierFlagsFromSymbol(s *ast.Symbol) ast.ModifierFlags { func (c *Checker) WasCanceled() bool { return c.wasCanceled } + +func (c *Checker) GetBaseConstraintOfType(t *Type) *Type { + return c.getBaseConstraintOfType(t) +} diff --git a/internal/checker/flow.go b/internal/checker/flow.go index 7ad7fd2396..e8d9daec2e 100644 --- a/internal/checker/flow.go +++ b/internal/checker/flow.go @@ -912,7 +912,7 @@ func (c *Checker) getNarrowedTypeWorker(t *Type, candidate *Type, assumeTrue boo } return c.mapType(t, func(t *Type) *Type { if c.maybeTypeOfKind(t, TypeFlagsInstantiable) { - constraint := c.GetBaseConstraintOfType(t) + constraint := c.getBaseConstraintOfType(t) if constraint == nil || isRelated(n, constraint) { return c.getIntersectionType([]*Type{t, n}) } diff --git a/internal/checker/inference.go b/internal/checker/inference.go index ea506f51c6..80e465aada 100644 --- a/internal/checker/inference.go +++ b/internal/checker/inference.go @@ -520,7 +520,7 @@ func (c *Checker) inferToTemplateLiteralType(n *InferenceState, source *Type, ta // allowed template literal placeholder types, infer from a literal type corresponding to the constraint. if source.flags&TypeFlagsStringLiteral != 0 && target.flags&TypeFlagsTypeVariable != 0 { if inferenceContext := getInferenceInfoForType(n, target); inferenceContext != nil { - if constraint := c.GetBaseConstraintOfType(inferenceContext.typeParameter); constraint != nil && !IsTypeAny(constraint) { + if constraint := c.getBaseConstraintOfType(inferenceContext.typeParameter); constraint != nil && !IsTypeAny(constraint) { allTypeFlags := TypeFlagsNone for _, t := range constraint.Distributed() { allTypeFlags |= t.flags @@ -702,7 +702,7 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target * // Middle of target is [...T, ...rest] and source is tuple type // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T if info := getInferenceInfoForType(n, elementTypes[startLength]); info != nil { - constraint := c.GetBaseConstraintOfType(info.typeParameter) + constraint := c.getBaseConstraintOfType(info.typeParameter) if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 { impliedArity := constraint.TargetTupleType().fixedLength c.inferFromTypes(n, c.sliceTupleType(source, startLength, sourceArity-(startLength+impliedArity)), elementTypes[startLength]) @@ -713,7 +713,7 @@ func (c *Checker) inferFromObjectTypes(n *InferenceState, source *Type, target * // Middle of target is [...rest, ...T] and source is tuple type // if T is constrained by a fixed-size tuple we might be able to use its arity to infer T if info := getInferenceInfoForType(n, elementTypes[startLength+1]); info != nil { - constraint := c.GetBaseConstraintOfType(info.typeParameter) + constraint := c.getBaseConstraintOfType(info.typeParameter) if constraint != nil && isTupleType(constraint) && constraint.TargetTupleType().combinedFlags&ElementFlagsVariable == 0 { impliedArity := constraint.TargetTupleType().fixedLength endIndex := sourceArity - getEndElementCount(target.TargetTupleType(), ElementFlagsFixed) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 92f0bf605a..56dfb8cdd8 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -2837,7 +2837,7 @@ func (r *Relater) unionOrIntersectionRelatedTo(source *Type, target *Type, repor if r.relation == r.c.comparableRelation && target.flags&TypeFlagsPrimitive != 0 { constraints := core.SameMap(source.Types(), func(t *Type) *Type { if t.flags&TypeFlagsInstantiable != 0 { - constraint := r.c.GetBaseConstraintOfType(t) + constraint := r.c.getBaseConstraintOfType(t) if constraint != nil { return constraint } @@ -3704,7 +3704,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo } case source.flags&TypeFlagsTemplateLiteral != 0 && target.flags&TypeFlagsObject == 0: if target.flags&TypeFlagsTemplateLiteral == 0 { - constraint := r.c.GetBaseConstraintOfType(source) + constraint := r.c.getBaseConstraintOfType(source) if constraint != nil && constraint != source { result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors) if result != TernaryFalse { @@ -3722,7 +3722,7 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo return result } } else { - constraint := r.c.GetBaseConstraintOfType(source) + constraint := r.c.getBaseConstraintOfType(source) if constraint != nil { result = r.isRelatedTo(constraint, target, RecursionFlagsSource, reportErrors) if result != TernaryFalse { @@ -4678,7 +4678,7 @@ func (r *Relater) reportRelationError(message *diagnostics.Message, source *Type targetFlags = target.flags } if targetFlags&TypeFlagsTypeParameter != 0 && target != r.c.markerSuperTypeForCheck && target != r.c.markerSubTypeForCheck { - constraint := r.c.GetBaseConstraintOfType(target) + constraint := r.c.getBaseConstraintOfType(target) switch { case constraint != nil && r.c.isTypeAssignableTo(generalizedSource, constraint): r.reportError(diagnostics.X_0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, generalizedSourceType, targetType, r.c.TypeToString(constraint)) @@ -4880,7 +4880,7 @@ func (c *Checker) isTypeDerivedFrom(source *Type, target *Type) bool { return c.isTypeDerivedFrom(t, target) }) case source.flags&TypeFlagsInstantiableNonPrimitive != 0: - constraint := c.GetBaseConstraintOfType(source) + constraint := c.getBaseConstraintOfType(source) if constraint == nil { constraint = c.unknownType } diff --git a/internal/ls/utilities.go b/internal/ls/utilities.go index 2b0b301fcd..457ca3c35a 100644 --- a/internal/ls/utilities.go +++ b/internal/ls/utilities.go @@ -748,7 +748,7 @@ func getAllSuperTypeNodes(node *ast.Node) []*ast.TypeNode { func skipConstraint(t *checker.Type, typeChecker *checker.Checker) *checker.Type { if t.IsTypeParameter() { - c := typeChecker.GetBaseConstraintOfType(t) + c := typeChecker.getBaseConstraintOfType(t) if c != nil { return c } From 7601c91567050cc67156de15095fd252c1b12026 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 20 May 2025 19:43:22 -0700 Subject: [PATCH 07/10] fix gettypechecker usage --- internal/ls/completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 268a722965..3c9d760e3b 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -1495,7 +1495,7 @@ func (l *LanguageService) completionInfoFromData( isNewIdentifierLocation := data.isNewIdentifierLocation contextToken := data.contextToken literals := data.literals - typeChecker, done := program.GetTypeChecker(ctx) + typeChecker, done := program.GetTypeCheckerForFile(ctx, file) defer done() // Verify if the file is JSX language variant From 6208c6230fd0402b2a4ca3673a81070e97f92c54 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 21 May 2025 11:27:46 -0700 Subject: [PATCH 08/10] refactor string literal completions struct --- internal/ls/string_completions.go | 123 +++++++++++++++++++----------- 1 file changed, 79 insertions(+), 44 deletions(-) diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index b990f3be80..6fc2567ea1 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -25,8 +25,6 @@ type completionsFromProperties struct { hasIndexSignature bool } -type completionsFromPaths = []*pathCompletion - type pathCompletion struct { name string // ScriptElementKindScriptElement | ScriptElementKindDirectory | ScriptElementKindExternalModuleName @@ -35,8 +33,11 @@ type pathCompletion struct { textRange *core.TextRange } -// *completionsFromTypes | *completionsFromProperties | completionsFromPaths -type stringLiteralCompletions = any +type stringLiteralCompletions struct { + fromTypes *completionsFromTypes + fromProperties *completionsFromProperties + fromPaths []*pathCompletion +} func (l *LanguageService) getStringLiteralCompletions( ctx context.Context, @@ -77,7 +78,7 @@ func (l *LanguageService) getStringLiteralCompletions( func (l *LanguageService) convertStringLiteralCompletions( ctx context.Context, - completion stringLiteralCompletions, + completion *stringLiteralCompletions, contextToken *ast.StringLiteralLike, file *ast.SourceFile, position int, @@ -91,10 +92,12 @@ func (l *LanguageService) convertStringLiteralCompletions( } optionalReplacementRange := l.createRangeFromStringLiteralLikeContent(file, contextToken, position) - switch completion := completion.(type) { - case completionsFromPaths: + switch { + case completion.fromPaths != nil: + completion := completion.fromPaths return l.convertPathCompletions(completion, file, position, clientOptions) - case *completionsFromProperties: + case completion.fromProperties != nil: + completion := completion.fromProperties data := &completionDataData{ symbols: completion.symbols, completionKind: CompletionKindString, @@ -122,7 +125,8 @@ func (l *LanguageService) convertStringLiteralCompletions( ItemDefaults: itemDefaults, Items: items, } - case *completionsFromTypes: + case completion.fromTypes != nil: + completion := completion.fromTypes var quoteChar printer.QuoteChar if contextToken.Kind == ast.KindNoSubstitutionTemplateLiteral { quoteChar = printer.QuoteCharBacktick @@ -167,7 +171,7 @@ func (l *LanguageService) convertStringLiteralCompletions( } func (l *LanguageService) convertPathCompletions( - pathCompletions completionsFromPaths, + pathCompletions []*pathCompletion, file *ast.SourceFile, position int, clientOptions *lsproto.CompletionClientCapabilities, @@ -212,7 +216,7 @@ func (l *LanguageService) getStringLiteralCompletionEntries( position int, program *compiler.Program, preferences *UserPreferences, -) stringLiteralCompletions { +) *stringLiteralCompletions { typeChecker, done := program.GetTypeChecker(ctx) done() parent := walkUpParentheses(node.Parent) @@ -242,13 +246,19 @@ func (l *LanguageService) getStringLiteralCompletionEntries( // foo({ // '/*completion position*/' // }); - return stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent) + return &stringLiteralCompletions{ + fromProperties: stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent), + } } result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) if result != nil { - return result + return &stringLiteralCompletions{ + fromTypes: result, + } + } + return &stringLiteralCompletions{ + fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker), } - return fromContextualType(checker.ContextFlagsNone, node, typeChecker) case ast.KindElementAccessExpression: expression := parent.Expression() argumentExpression := parent.AsElementAccessExpression().ArgumentExpression @@ -260,7 +270,9 @@ func (l *LanguageService) getStringLiteralCompletionEntries( // let a: A; // a['/*completion position*/'] t := typeChecker.GetTypeAtLocation(expression) - return stringLiteralCompletionsFromProperties(t, typeChecker) + return &stringLiteralCompletions{ + fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker), + } } return nil case ast.KindCallExpression, ast.KindNewExpression, ast.KindJsxAttribute: @@ -291,9 +303,11 @@ func (l *LanguageService) getStringLiteralCompletionEntries( literals := core.Filter(contextualTypes.types, func(t *checker.StringLiteralType) bool { return !tracker.hasValue(t.AsLiteralType().Value()) }) - return &completionsFromTypes{ - types: literals, - isNewIdentifier: false, + return &stringLiteralCompletions{ + fromTypes: &completionsFromTypes{ + types: literals, + isNewIdentifier: false, + }, } case ast.KindImportSpecifier, ast.KindExportSpecifier: // Complete string aliases in `import { "|" } from` and `export { "|" } from` @@ -325,16 +339,22 @@ func (l *LanguageService) getStringLiteralCompletionEntries( uniques := core.Filter(exports, func(e *ast.Symbol) bool { return e.Name != ast.InternalSymbolNameDefault && !existing.Has(e.Name) }) - return &completionsFromProperties{ - symbols: uniques, - hasIndexSignature: false, + return &stringLiteralCompletions{ + fromProperties: &completionsFromProperties{ + symbols: uniques, + hasIndexSignature: false, + }, } default: result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker) if result != nil { - return result + return &stringLiteralCompletions{ + fromTypes: result, + } + } + return &stringLiteralCompletions{ + fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker), } - return fromContextualType(checker.ContextFlagsNone, node, typeChecker) } } @@ -351,15 +371,22 @@ func fromContextualType(contextFlags checker.ContextFlags, node *ast.Node, typeC } } -func fromUnionableLiteralType(grandparent *ast.Node, parent *ast.Node, position int, typeChecker *checker.Checker) stringLiteralCompletions { +func fromUnionableLiteralType( + grandparent *ast.Node, + parent *ast.Node, + position int, + typeChecker *checker.Checker, +) *stringLiteralCompletions { switch grandparent.Kind { case ast.KindExpressionWithTypeArguments, ast.KindTypeReference: typeArgument := ast.FindAncestor(parent, func(n *ast.Node) bool { return n.Parent == grandparent }) if typeArgument != nil { t := typeChecker.GetTypeArgumentConstraint(typeArgument) - return &completionsFromTypes{ - types: getStringLiteralTypes(t, nil, typeChecker), - isNewIdentifier: false, + return &stringLiteralCompletions{ + fromTypes: &completionsFromTypes{ + types: getStringLiteralTypes(t, nil, typeChecker), + isNewIdentifier: false, + }, } } return nil @@ -376,7 +403,9 @@ func fromUnionableLiteralType(grandparent *ast.Node, parent *ast.Node, position return nil } t := typeChecker.GetTypeFromTypeNode(objectType) - return stringLiteralCompletionsFromProperties(t, typeChecker) + return &stringLiteralCompletions{ + fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker), + } case ast.KindUnionType: result := fromUnionableLiteralType( walkUpParentheses(grandparent.Parent), @@ -387,24 +416,30 @@ func fromUnionableLiteralType(grandparent *ast.Node, parent *ast.Node, position return nil } alreadyUsedTypes := getAlreadyUsedTypesInStringLiteralUnion(grandparent, parent) - switch result := result.(type) { - case *completionsFromProperties: - return &completionsFromProperties{ - symbols: core.Filter( - result.symbols, - func(s *ast.Symbol) bool { return !slices.Contains(alreadyUsedTypes, s.Name) }, - ), - hasIndexSignature: result.hasIndexSignature, + switch { + case result.fromProperties != nil: + result := result.fromProperties + return &stringLiteralCompletions{ + fromProperties: &completionsFromProperties{ + symbols: core.Filter( + result.symbols, + func(s *ast.Symbol) bool { return !slices.Contains(alreadyUsedTypes, s.Name) }, + ), + hasIndexSignature: result.hasIndexSignature, + }, } - case *completionsFromTypes: - return &completionsFromTypes{ - types: core.Filter(result.types, func(t *checker.StringLiteralType) bool { - return !slices.Contains(alreadyUsedTypes, t.AsLiteralType().Value().(string)) - }), - isNewIdentifier: false, + case result.fromTypes != nil: + result := result.fromTypes + return &stringLiteralCompletions{ + fromTypes: &completionsFromTypes{ + types: core.Filter(result.types, func(t *checker.StringLiteralType) bool { + return !slices.Contains(alreadyUsedTypes, t.AsLiteralType().Value().(string)) + }), + isNewIdentifier: false, + }, } default: - panic(fmt.Sprintf("Unexpected result type: %T", result)) + return nil } default: return nil @@ -447,7 +482,7 @@ func getStringLiteralCompletionsFromModuleNames( node *ast.LiteralExpression, program *compiler.Program, preferences *UserPreferences, -) stringLiteralCompletions { +) *stringLiteralCompletions { // !!! needs `getModeForUsageLocationWorker` return nil } From e05bbf04369b7067a16b27fc4fe7e273f0fe7513 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 21 May 2025 11:35:04 -0700 Subject: [PATCH 09/10] add missing default --- internal/ls/string_completions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 6fc2567ea1..d1bf71c082 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -166,7 +166,7 @@ func (l *LanguageService) convertStringLiteralCompletions( Items: items, } default: - panic(fmt.Sprintf("Unexpected completion type: %T", completion)) + return nil } } From bbf5fa19390ae929390bd4b6c55e1d5d1b5adfdd Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 23 May 2025 09:36:05 -0700 Subject: [PATCH 10/10] add string completions test for property --- internal/ls/completions_test.go | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/internal/ls/completions_test.go b/internal/ls/completions_test.go index 0197bbdaf4..5586887082 100644 --- a/internal/ls/completions_test.go +++ b/internal/ls/completions_test.go @@ -1869,6 +1869,77 @@ switch (x) { }, }, }, + { + name: "completionForQuotedPropertyInPropertyAssignment1", + files: map[string]string{ + defaultMainFileName: `export interface Configfiles { + jspm: string; + 'jspm:browser': string; +} + +let files: Configfiles; +files = { + /*0*/: '', + '/*1*/': '' +}`, + }, + expectedResult: map[string]*testCaseResult{ + "0": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: `"jspm:browser"`, + Kind: fieldKind, + SortText: sortTextLocationPriority, + }, + { + Label: "jspm", + Kind: fieldKind, + SortText: sortTextLocationPriority, + }, + }, + }, + }, + "1": { + list: &lsproto.CompletionList{ + IsIncomplete: false, + ItemDefaults: itemDefaults, + Items: []*lsproto.CompletionItem{ + { + Label: "jspm", + Kind: fieldKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "jspm", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 8, Character: 4}, + End: lsproto.Position{Line: 8, Character: 4}, + }, + }, + }, + }, + { + Label: "jspm:browser", + Kind: fieldKind, + SortText: sortTextLocationPriority, + TextEdit: &lsproto.TextEditOrInsertReplaceEdit{ + TextEdit: &lsproto.TextEdit{ + NewText: "jspm:browser", + Range: lsproto.Range{ + Start: lsproto.Position{Line: 8, Character: 4}, + End: lsproto.Position{Line: 8, Character: 4}, + }, + }, + }, + }, + }, + }, + }, + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) {