732 lines
22 KiB
Go
732 lines
22 KiB
Go
package ls
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/compiler"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/printer"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
|
)
|
|
|
|
type completionsFromTypes struct {
|
|
types []*checker.StringLiteralType
|
|
isNewIdentifier bool
|
|
}
|
|
|
|
type completionsFromProperties struct {
|
|
symbols []*ast.Symbol
|
|
hasIndexSignature bool
|
|
}
|
|
|
|
type pathCompletion struct {
|
|
name string
|
|
// ScriptElementKindScriptElement | ScriptElementKindDirectory | ScriptElementKindExternalModuleName
|
|
kind ScriptElementKind
|
|
extension string
|
|
textRange *core.TextRange
|
|
}
|
|
|
|
type stringLiteralCompletions struct {
|
|
fromTypes *completionsFromTypes
|
|
fromProperties *completionsFromProperties
|
|
fromPaths []*pathCompletion
|
|
}
|
|
|
|
func (l *LanguageService) getStringLiteralCompletions(
|
|
ctx context.Context,
|
|
file *ast.SourceFile,
|
|
position int,
|
|
contextToken *ast.Node,
|
|
compilerOptions *core.CompilerOptions,
|
|
preferences *UserPreferences,
|
|
clientOptions *lsproto.CompletionClientCapabilities,
|
|
) *lsproto.CompletionList {
|
|
// !!! reference comment
|
|
if IsInString(file, position, contextToken) {
|
|
if contextToken == nil || !ast.IsStringLiteralLike(contextToken) {
|
|
return nil
|
|
}
|
|
entries := l.getStringLiteralCompletionEntries(
|
|
ctx,
|
|
file,
|
|
contextToken,
|
|
position,
|
|
preferences)
|
|
return l.convertStringLiteralCompletions(
|
|
ctx,
|
|
entries,
|
|
contextToken,
|
|
file,
|
|
position,
|
|
compilerOptions,
|
|
preferences,
|
|
clientOptions,
|
|
)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LanguageService) convertStringLiteralCompletions(
|
|
ctx context.Context,
|
|
completion *stringLiteralCompletions,
|
|
contextToken *ast.StringLiteralLike,
|
|
file *ast.SourceFile,
|
|
position int,
|
|
options *core.CompilerOptions,
|
|
preferences *UserPreferences,
|
|
clientOptions *lsproto.CompletionClientCapabilities,
|
|
) *lsproto.CompletionList {
|
|
if completion == nil {
|
|
return nil
|
|
}
|
|
|
|
optionalReplacementRange := l.createRangeFromStringLiteralLikeContent(file, contextToken, position)
|
|
switch {
|
|
case completion.fromPaths != nil:
|
|
completion := completion.fromPaths
|
|
return l.convertPathCompletions(completion, file, position, clientOptions)
|
|
case completion.fromProperties != nil:
|
|
completion := completion.fromProperties
|
|
data := &completionDataData{
|
|
symbols: completion.symbols,
|
|
completionKind: CompletionKindString,
|
|
isNewIdentifierLocation: completion.hasIndexSignature,
|
|
location: file.AsNode(),
|
|
contextToken: contextToken,
|
|
}
|
|
_, items := l.getCompletionEntriesFromSymbols(
|
|
ctx,
|
|
data,
|
|
contextToken, /*replacementToken*/
|
|
position,
|
|
file,
|
|
preferences,
|
|
options,
|
|
clientOptions,
|
|
)
|
|
defaultCommitCharacters := getDefaultCommitCharacters(completion.hasIndexSignature)
|
|
itemDefaults := l.setItemDefaults(
|
|
clientOptions,
|
|
position,
|
|
file,
|
|
items,
|
|
&defaultCommitCharacters,
|
|
optionalReplacementRange,
|
|
)
|
|
return &lsproto.CompletionList{
|
|
IsIncomplete: false,
|
|
ItemDefaults: itemDefaults,
|
|
Items: items,
|
|
}
|
|
case completion.fromTypes != nil:
|
|
completion := completion.fromTypes
|
|
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,
|
|
collections.Set[ScriptElementKindModifier]{},
|
|
l.getReplacementRangeForContextToken(file, contextToken, position),
|
|
nil, /*commitCharacters*/
|
|
nil, /*labelDetails*/
|
|
file,
|
|
position,
|
|
clientOptions,
|
|
false, /*isMemberCompletion*/
|
|
false, /*isSnippet*/
|
|
false, /*hasAction*/
|
|
false, /*preselect*/
|
|
"", /*source*/
|
|
nil, /*autoImportEntryData*/
|
|
)
|
|
})
|
|
defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier)
|
|
itemDefaults := l.setItemDefaults(
|
|
clientOptions,
|
|
position,
|
|
file,
|
|
items,
|
|
&defaultCommitCharacters,
|
|
nil, /*optionalReplacementSpan*/
|
|
)
|
|
return &lsproto.CompletionList{
|
|
IsIncomplete: false,
|
|
ItemDefaults: itemDefaults,
|
|
Items: items,
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (l *LanguageService) convertPathCompletions(
|
|
pathCompletions []*pathCompletion,
|
|
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,
|
|
*collections.NewSetFromItems(kindModifiersFromExtension(pathCompletion.extension)),
|
|
replacementSpan,
|
|
nil, /*commitCharacters*/
|
|
nil, /*labelDetails*/
|
|
file,
|
|
position,
|
|
clientOptions,
|
|
false, /*isMemberCompletion*/
|
|
false, /*isSnippet*/
|
|
false, /*hasAction*/
|
|
false, /*preselect*/
|
|
"", /*source*/
|
|
nil, /*autoImportEntryData*/
|
|
)
|
|
})
|
|
itemDefaults := l.setItemDefaults(
|
|
clientOptions,
|
|
position,
|
|
file,
|
|
items,
|
|
&defaultCommitCharacters,
|
|
nil, /*optionalReplacementSpan*/
|
|
)
|
|
return &lsproto.CompletionList{
|
|
IsIncomplete: false,
|
|
ItemDefaults: itemDefaults,
|
|
Items: items,
|
|
}
|
|
}
|
|
|
|
func (l *LanguageService) getStringLiteralCompletionEntries(
|
|
ctx context.Context,
|
|
file *ast.SourceFile,
|
|
node *ast.StringLiteralLike,
|
|
position int,
|
|
preferences *UserPreferences,
|
|
) *stringLiteralCompletions {
|
|
typeChecker, done := l.GetProgram().GetTypeCheckerForFile(ctx, file)
|
|
defer done()
|
|
parent := walkUpParentheses(node.Parent)
|
|
switch parent.Kind {
|
|
case ast.KindLiteralType:
|
|
grandparent := walkUpParentheses(parent.Parent)
|
|
if grandparent.Kind == ast.KindImportType {
|
|
return getStringLiteralCompletionsFromModuleNames(
|
|
file,
|
|
node,
|
|
l.GetProgram(),
|
|
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 &stringLiteralCompletions{
|
|
fromProperties: stringLiteralCompletionsForObjectLiteral(typeChecker, parent.Parent),
|
|
}
|
|
}
|
|
result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker)
|
|
if result != nil {
|
|
return &stringLiteralCompletions{
|
|
fromTypes: result,
|
|
}
|
|
}
|
|
return &stringLiteralCompletions{
|
|
fromTypes: 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 &stringLiteralCompletions{
|
|
fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker),
|
|
}
|
|
}
|
|
return nil
|
|
case ast.KindCallExpression, ast.KindNewExpression, ast.KindJsxAttribute:
|
|
if !isRequireCallArgument(node) && !ast.IsImportCall(parent) {
|
|
var argumentNode *ast.Node
|
|
if parent.Kind == ast.KindJsxAttribute {
|
|
argumentNode = parent.Parent
|
|
} else {
|
|
argumentNode = node
|
|
}
|
|
argumentInfo := getArgumentInfoForCompletions(argumentNode, position, file, typeChecker)
|
|
// Get string literal completions from specialized signatures of the target
|
|
// i.e. declare function f(a: 'A');
|
|
// f("/*completion position*/")
|
|
if argumentInfo == nil {
|
|
return nil
|
|
}
|
|
|
|
result := getStringLiteralCompletionsFromSignature(argumentInfo.invocation, node, argumentInfo, typeChecker)
|
|
if result != nil {
|
|
return &stringLiteralCompletions{
|
|
fromTypes: result,
|
|
}
|
|
}
|
|
return &stringLiteralCompletions{
|
|
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
}
|
|
}
|
|
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, l.GetProgram(), 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 &stringLiteralCompletions{
|
|
fromTypes: &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 := collections.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 &stringLiteralCompletions{
|
|
fromProperties: &completionsFromProperties{
|
|
symbols: uniques,
|
|
hasIndexSignature: false,
|
|
},
|
|
}
|
|
case ast.KindBinaryExpression:
|
|
if parent.AsBinaryExpression().OperatorToken.Kind == ast.KindInKeyword {
|
|
t := typeChecker.GetTypeAtLocation(parent.AsBinaryExpression().Right)
|
|
properties := getPropertiesForCompletion(t, typeChecker)
|
|
return &stringLiteralCompletions{
|
|
fromProperties: &completionsFromProperties{
|
|
symbols: core.Filter(properties, func(s *ast.Symbol) bool {
|
|
return s.ValueDeclaration == nil || !ast.IsPrivateIdentifierClassElementDeclaration(s.ValueDeclaration)
|
|
}),
|
|
hasIndexSignature: false,
|
|
},
|
|
}
|
|
}
|
|
return &stringLiteralCompletions{
|
|
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
}
|
|
default:
|
|
result := fromContextualType(checker.ContextFlagsCompletions, node, typeChecker)
|
|
if result != nil {
|
|
return &stringLiteralCompletions{
|
|
fromTypes: result,
|
|
}
|
|
}
|
|
return &stringLiteralCompletions{
|
|
fromTypes: fromContextualType(checker.ContextFlagsNone, node, typeChecker),
|
|
}
|
|
}
|
|
}
|
|
|
|
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 &stringLiteralCompletions{
|
|
fromTypes: &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 &stringLiteralCompletions{
|
|
fromProperties: stringLiteralCompletionsFromProperties(t, typeChecker),
|
|
}
|
|
case ast.KindUnionType:
|
|
result := fromUnionableLiteralType(
|
|
walkUpParentheses(grandparent.Parent),
|
|
parent,
|
|
position,
|
|
typeChecker)
|
|
if result == nil {
|
|
return nil
|
|
}
|
|
alreadyUsedTypes := getAlreadyUsedTypesInStringLiteralUnion(grandparent, parent)
|
|
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 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:
|
|
return nil
|
|
}
|
|
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 {
|
|
// !!! needs `getModeForUsageLocationWorker`
|
|
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 *collections.Set[string], typeChecker *checker.Checker) []*checker.StringLiteralType {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
if uniques == nil {
|
|
uniques = &collections.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
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
}
|
|
|
|
func getStringLiteralCompletionsFromSignature(
|
|
call *ast.CallLikeExpression,
|
|
arg *ast.StringLiteralLike,
|
|
argumentInfo *argumentInfoForCompletions,
|
|
typeChecker *checker.Checker,
|
|
) *completionsFromTypes {
|
|
isNewIdentifier := false
|
|
uniques := collections.Set[string]{}
|
|
var editingArgument *ast.Node
|
|
if ast.IsJsxOpeningLikeElement(call) {
|
|
editingArgument = ast.FindAncestor(arg.Parent, ast.IsJsxAttribute)
|
|
if editingArgument == nil {
|
|
panic("Expected jsx opening-like element to have a jsx attribute as ancestor.")
|
|
}
|
|
} else {
|
|
editingArgument = arg
|
|
}
|
|
candidates := typeChecker.GetCandidateSignaturesForStringLiteralCompletions(call, editingArgument)
|
|
var types []*checker.StringLiteralType
|
|
for _, candidate := range candidates {
|
|
if !candidate.HasRestParameter() && argumentInfo.argumentCount > len(candidate.Parameters()) {
|
|
continue
|
|
}
|
|
t := typeChecker.GetTypeParameterAtPosition(candidate, argumentInfo.argumentIndex)
|
|
if ast.IsJsxOpeningLikeElement(call) {
|
|
propType := typeChecker.GetTypeOfPropertyOfType(t, editingArgument.AsJsxAttribute().Name().Text())
|
|
if propType != nil {
|
|
t = propType
|
|
}
|
|
}
|
|
isNewIdentifier = isNewIdentifier || t.IsString()
|
|
types = append(types, getStringLiteralTypes(t, &uniques, typeChecker)...)
|
|
}
|
|
if len(types) > 0 {
|
|
return &completionsFromTypes{
|
|
types: types,
|
|
isNewIdentifier: isNewIdentifier,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LanguageService) getStringLiteralCompletionDetails(
|
|
ctx context.Context,
|
|
checker *checker.Checker,
|
|
item *lsproto.CompletionItem,
|
|
name string,
|
|
file *ast.SourceFile,
|
|
position int,
|
|
contextToken *ast.Node,
|
|
preferences *UserPreferences,
|
|
) *lsproto.CompletionItem {
|
|
if contextToken == nil || !ast.IsStringLiteralLike(contextToken) {
|
|
return item
|
|
}
|
|
completions := l.getStringLiteralCompletionEntries(
|
|
ctx,
|
|
file,
|
|
contextToken,
|
|
position,
|
|
preferences,
|
|
)
|
|
if completions == nil {
|
|
return item
|
|
}
|
|
return stringLiteralCompletionDetails(item, name, contextToken, completions, file, checker)
|
|
}
|
|
|
|
func stringLiteralCompletionDetails(
|
|
item *lsproto.CompletionItem,
|
|
name string,
|
|
location *ast.Node,
|
|
completion *stringLiteralCompletions,
|
|
file *ast.SourceFile,
|
|
checker *checker.Checker,
|
|
) *lsproto.CompletionItem {
|
|
switch {
|
|
case completion.fromPaths != nil:
|
|
pathCompletions := completion.fromPaths
|
|
for _, pathCompletion := range pathCompletions {
|
|
if pathCompletion.name == name {
|
|
return createCompletionDetails(item, name, "" /*documentation*/)
|
|
}
|
|
}
|
|
case completion.fromProperties != nil:
|
|
properties := completion.fromProperties
|
|
for _, symbol := range properties.symbols {
|
|
if symbol.Name == name {
|
|
return createCompletionDetailsForSymbol(item, symbol, checker, location, nil /*actions*/)
|
|
}
|
|
}
|
|
case completion.fromTypes != nil:
|
|
types := completion.fromTypes
|
|
for _, t := range types.types {
|
|
if t.AsLiteralType().Value().(string) == name {
|
|
return createCompletionDetails(item, name, "" /*documentation*/)
|
|
}
|
|
}
|
|
}
|
|
return item
|
|
}
|