Files
kjol/tools/tsgo/internal/ls/codeactions_missingmemberfixer.go
2026-07-09 16:50:43 -04:00

499 lines
20 KiB
Go

package ls
import (
"strconv"
"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/diagnostics"
"github.com/microsoft/typescript-go/internal/locale"
"github.com/microsoft/typescript-go/internal/ls/autoimport"
"github.com/microsoft/typescript-go/internal/ls/change"
"github.com/microsoft/typescript-go/internal/ls/lsutil"
"github.com/microsoft/typescript-go/internal/nodebuilder"
)
type preserveOptionalFlags int
const (
preserveOptionalFlagsMethod preserveOptionalFlags = 1 << iota
preserveOptionalFlagsProperty
preserveOptionalFlagsAll = preserveOptionalFlagsMethod | preserveOptionalFlagsProperty
)
type missingMemberFixer struct {
changeTracker *change.Tracker
typeChecker *checker.Checker
program *compiler.Program
preferences lsutil.UserPreferences
importAdder autoimport.ImportAdder
locale locale.Locale
}
func newMissingMemberFixer(changeTracker *change.Tracker, program *compiler.Program, typeChecker *checker.Checker, preferences lsutil.UserPreferences, importAdder autoimport.ImportAdder, locale locale.Locale) *missingMemberFixer {
return &missingMemberFixer{
changeTracker: changeTracker,
typeChecker: typeChecker,
program: program,
preferences: preferences,
importAdder: importAdder,
locale: locale,
}
}
func (f *missingMemberFixer) createNodeBuilder() (*checker.NodeBuilder, map[*ast.IdentifierNode]*ast.Symbol) {
idToSymbol := make(map[*ast.IdentifierNode]*ast.Symbol)
nodeBuilder := checker.NewNodeBuilderEx(f.typeChecker, f.changeTracker.EmitContext, idToSymbol)
return nodeBuilder, idToSymbol
}
func (f *missingMemberFixer) createMemberFromSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, sourceFile *ast.SourceFile, body *ast.FunctionBody, preserveOptional preserveOptionalFlags) []*ast.Node {
declarations := symbol.Declarations
declaration := core.FirstOrNil(declarations)
quotePreference := lsutil.GetQuotePreference(sourceFile, f.preferences)
ambient := enclosingDeclaration.Flags&ast.NodeFlagsAmbient != 0
optional := symbol.Flags&ast.SymbolFlagsOptional != 0
kind := ast.KindPropertySignature
if declaration != nil {
kind = declaration.Kind
}
declarationName := createDeclarationName(f.changeTracker.NodeFactory, f.typeChecker, symbol, declaration)
modifiers := f.createModifiers(symbol, declaration)
flags := nodebuilder.FlagsNoTruncation
if quotePreference == lsutil.QuotePreferenceSingle {
flags |= nodebuilder.FlagsUseSingleQuotesForStringLiteralType
}
t := f.typeChecker.GetWidenedType(f.typeChecker.GetTypeOfSymbolAtLocation(symbol, enclosingDeclaration))
var nodes []*ast.Node
switch kind {
case ast.KindPropertySignature, ast.KindPropertyDeclaration:
nodeBuilder, idToSymbol := f.createNodeBuilder()
typeNode := f.createTypeNode(t, enclosingDeclaration, flags, nodeBuilder, idToSymbol)
var questionToken *ast.TokenNode
if optional && preserveOptional&preserveOptionalFlagsProperty != 0 {
questionToken = f.changeTracker.NodeFactory.NewToken(ast.KindQuestionToken)
}
return append(nodes, f.changeTracker.NodeFactory.NewPropertyDeclaration(modifiers, createPropertyName(f.changeTracker.NodeFactory, declarationName, quotePreference), questionToken, typeNode, nil /*initializer*/))
case ast.KindGetAccessor, ast.KindSetAccessor:
nodeBuilder, idToSymbol := f.createNodeBuilder()
accessors := ast.GetAllAccessorDeclarations(symbol.Declarations, declaration)
var orderedAccessors []*ast.Node
if accessors.SecondAccessor == nil {
orderedAccessors = append(orderedAccessors, accessors.FirstAccessor)
} else {
orderedAccessors = append(orderedAccessors, accessors.FirstAccessor, accessors.SecondAccessor)
}
for _, accessor := range orderedAccessors {
if ast.IsGetAccessorDeclaration(accessor) {
nodes = append(
nodes,
f.changeTracker.NodeFactory.NewGetAccessorDeclaration(
modifiers, createPropertyName(f.changeTracker.NodeFactory, declarationName, quotePreference),
nil /*typeParameters*/, nil /*parameters*/, f.createTypeNode(t, enclosingDeclaration, flags, nodeBuilder, idToSymbol), nil /*fullSignature*/, f.createBody(body, ambient, quotePreference),
),
)
}
if ast.IsSetAccessorDeclaration(accessor) {
parameter := checker.GetSetAccessorValueParameter(accessor)
if parameter == nil {
panic("Expected set accessor to have a parameter.")
}
nodes = append(
nodes, f.changeTracker.NodeFactory.NewSetAccessorDeclaration(
modifiers, createPropertyName(f.changeTracker.NodeFactory, declarationName, quotePreference),
nil /*typeParameters*/, createDummyParameters(f.changeTracker.NodeFactory, 1, []string{parameter.Name().Text()}, []*ast.TypeNode{f.createTypeNode(t, enclosingDeclaration, flags, nodeBuilder, idToSymbol)}, 1, ast.IsInJSFile(enclosingDeclaration)),
nil /*type*/, nil /*fullSignature*/, f.createBody(body, ambient, quotePreference),
),
)
}
}
return nodes
case ast.KindMethodSignature, ast.KindMethodDeclaration:
signatures := f.getCallSignatures(t)
preserveOptional := optional && preserveOptional&preserveOptionalFlagsMethod != 0
if len(signatures) == 0 {
return nil
}
if len(declarations) == 1 {
method := f.createSignatureDeclarationFromSignature(core.FirstOrNil(signatures), ast.KindMethodDeclaration, sourceFile, enclosingDeclaration, f.createBody(body, ambient, quotePreference), modifiers, declarationName, preserveOptional)
if method != nil {
nodes = append(nodes, method)
}
return nodes
}
for _, signature := range signatures {
if signature.Declaration() != nil && signature.Declaration().Flags&ast.NodeFlagsAmbient != 0 {
continue
}
method := f.createSignatureDeclarationFromSignature(signature, ast.KindMethodDeclaration, sourceFile, enclosingDeclaration, nil, modifiers, declarationName, preserveOptional)
if method != nil {
nodes = append(nodes, method)
}
}
if ambient {
return nodes
}
if len(declarations) > len(signatures) {
signature := f.typeChecker.GetSignatureFromDeclaration(core.LastOrNil(declarations))
method := f.createSignatureDeclarationFromSignature(signature, ast.KindMethodDeclaration, sourceFile, enclosingDeclaration, f.createBody(body, ambient, quotePreference), modifiers, declarationName, preserveOptional)
if method != nil {
nodes = append(nodes, method)
}
} else {
method := f.createSignatureDeclarationFromSignatures(signatures, declarationName, preserveOptional, modifiers, quotePreference, body, enclosingDeclaration)
if method != nil {
nodes = append(nodes, method)
}
}
return nodes
}
return nil
}
func (f *missingMemberFixer) getCallSignatures(t *checker.Type) []*checker.Signature {
if t.IsUnion() {
return core.FlatMap(t.Types(), f.typeChecker.GetCallSignatures)
}
return f.typeChecker.GetCallSignatures(t)
}
func (f *missingMemberFixer) createTypeNode(t *checker.Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, nodeBuilder *checker.NodeBuilder, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
return f.importTypeNode(nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil /*tracker*/), idToSymbol)
}
func (f *missingMemberFixer) createModifiers(symbol *ast.Symbol, declaration *ast.Node) *ast.ModifierList {
modifierFlags := ast.ModifierFlagsNone
if declaration != nil {
effective := checker.GetDeclarationModifierFlagsFromSymbol(symbol)
modifierFlags = effective & ast.ModifierFlagsStatic
if effective&ast.ModifierFlagsPublic != 0 {
modifierFlags |= ast.ModifierFlagsPublic
} else if effective&ast.ModifierFlagsProtected != 0 {
modifierFlags |= ast.ModifierFlagsProtected
}
if ast.IsAutoAccessorPropertyDeclaration(declaration) {
modifierFlags |= ast.ModifierFlagsAccessor
}
}
if f.shouldAddOverrideKeyword(declaration) {
modifierFlags |= ast.ModifierFlagsOverride
}
if modifierFlags == ast.ModifierFlagsNone {
return nil
}
return f.changeTracker.NodeFactory.NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, f.changeTracker.NodeFactory.NewModifier))
}
func (f *missingMemberFixer) shouldAddOverrideKeyword(declaration *ast.Node) bool {
return declaration != nil && f.program.Options().NoImplicitOverride.IsTrue() && ast.HasAbstractModifier(declaration)
}
func (f *missingMemberFixer) createSignatureDeclarationFromSignature(signature *checker.Signature, kind ast.Kind, sourceFile *ast.SourceFile, enclosingDeclaration *ast.Node, body *ast.FunctionBody, modifiers *ast.ModifierList, name *ast.PropertyName, optional bool) *ast.Node {
quotePreference := lsutil.GetQuotePreference(sourceFile, f.preferences)
flags := nodebuilder.FlagsNoTruncation | nodebuilder.FlagsSuppressAnyReturnType | nodebuilder.FlagsAllowEmptyTuple
if quotePreference == lsutil.QuotePreferenceSingle {
flags |= nodebuilder.FlagsUseSingleQuotesForStringLiteralType
}
nodeBuilder, idToSymbol := f.createNodeBuilder()
signatureDeclaration := nodeBuilder.SignatureToSignatureDeclaration(signature, kind, enclosingDeclaration, flags, nodebuilder.InternalFlagsAllowUnresolvedNames, nil /*tracker*/)
if signatureDeclaration == nil {
return nil
}
isJS := ast.IsInJSFile(enclosingDeclaration)
parameters := signatureDeclaration.ParameterList()
typeParameters := core.IfElse(isJS, nil, signatureDeclaration.TypeParameterList())
typeNode := core.IfElse(isJS, nil, signatureDeclaration.Type())
if typeParameters != nil && len(typeParameters.Nodes) > 0 {
nodes := make([]*ast.Node, 0, len(typeParameters.Nodes))
for _, tp := range typeParameters.Nodes {
if tp == nil {
continue
}
if ast.IsTypeParameterDeclaration(tp) {
typeParameter := tp.AsTypeParameterDeclaration()
constraint := typeParameter.Constraint
if constraint != nil {
constraint = f.importTypeNode(constraint, idToSymbol)
}
defaultType := typeParameter.DefaultType
if defaultType != nil {
defaultType = f.importTypeNode(defaultType, idToSymbol)
}
nodes = append(nodes,
f.changeTracker.NodeFactory.UpdateTypeParameterDeclaration(typeParameter, typeParameter.Modifiers(), typeParameter.Name(), constraint, typeParameter.Expression, defaultType))
} else {
nodes = append(nodes, tp)
}
}
typeParameters = f.changeTracker.NodeFactory.NewNodeList(nodes)
}
if parameters != nil {
nodes := make([]*ast.Node, 0, len(parameters.Nodes))
for _, p := range parameters.Nodes {
if p == nil {
continue
}
parameter := p.AsParameterDeclaration()
parameterTypeNode := parameter.Type
if parameterTypeNode != nil {
parameterTypeNode = f.importTypeNode(parameterTypeNode, idToSymbol)
}
nodes = append(nodes,
f.changeTracker.NodeFactory.UpdateParameterDeclaration(parameter, parameter.Modifiers(), parameter.DotDotDotToken, parameter.Name(), core.IfElse(isJS, nil, parameter.QuestionToken), parameterTypeNode, parameter.Initializer))
}
parameters = f.changeTracker.NodeFactory.NewNodeList(nodes)
}
if typeNode != nil {
typeNode = f.importTypeNode(typeNode, idToSymbol)
}
var questionToken *ast.TokenNode
if optional {
questionToken = f.changeTracker.NodeFactory.NewToken(ast.KindQuestionToken)
}
switch kind {
case ast.KindFunctionExpression:
fn := signatureDeclaration.AsFunctionExpression()
return f.changeTracker.NodeFactory.UpdateFunctionExpression(fn, modifiers, fn.AsteriskToken, core.IfElse(name != nil && ast.IsIdentifier(name), name, nil), typeParameters, parameters, typeNode, fn.FullSignature, core.OrElse(body, fn.Body))
case ast.KindArrowFunction:
fn := signatureDeclaration.AsArrowFunction()
return f.changeTracker.NodeFactory.UpdateArrowFunction(fn, modifiers, typeParameters, parameters, typeNode, fn.FullSignature, fn.EqualsGreaterThanToken, core.OrElse(body, fn.Body))
case ast.KindMethodDeclaration:
method := signatureDeclaration.AsMethodDeclaration()
methodName := core.IfElse(name == nil, f.changeTracker.NodeFactory.NewIdentifier(""), createPropertyName(f.changeTracker.NodeFactory, name, quotePreference))
return f.changeTracker.NodeFactory.UpdateMethodDeclaration(method, modifiers, method.AsteriskToken, methodName, questionToken, typeParameters, parameters, typeNode, method.FullSignature, body)
case ast.KindFunctionDeclaration:
fn := signatureDeclaration.AsFunctionDeclaration()
return f.changeTracker.NodeFactory.UpdateFunctionDeclaration(fn, modifiers, fn.AsteriskToken, core.IfElse(name != nil && ast.IsIdentifier(name), name, nil), typeParameters, parameters, typeNode, fn.FullSignature, core.OrElse(body, fn.Body))
}
return nil
}
func (f *missingMemberFixer) createSignatureDeclarationFromSignatures(signatures []*checker.Signature, name *ast.PropertyName, optional bool, modifiers *ast.ModifierList, quotePreference lsutil.QuotePreference, body *ast.FunctionBody, enclosingDeclaration *ast.Node) *ast.Node {
if len(signatures) == 0 {
return nil
}
nodeBuilder, idToSymbol := f.createNodeBuilder()
maxArgsSignature := signatures[0]
minArgumentCount := signatures[0].MinArgumentCount()
hasRestParameter := false
for _, signature := range signatures {
minArgumentCount = min(minArgumentCount, signature.MinArgumentCount())
if signature.HasRestParameter() {
hasRestParameter = true
}
if len(signature.Parameters()) >= len(maxArgsSignature.Parameters()) && (!signature.HasRestParameter() || maxArgsSignature.HasRestParameter()) {
maxArgsSignature = signature
}
}
maxNonRestArgs := len(maxArgsSignature.Parameters()) - core.IfElse(maxArgsSignature.HasRestParameter(), 1, 0)
parameterNames := make([]string, 0, len(maxArgsSignature.Parameters()))
for _, symbol := range maxArgsSignature.Parameters() {
parameterNames = append(parameterNames, symbol.Name)
}
parameters := createDummyParameters(f.changeTracker.NodeFactory, maxNonRestArgs, parameterNames, nil /*types*/, minArgumentCount, ast.IsInJSFile(enclosingDeclaration))
if hasRestParameter {
restParameterName := "rest"
if maxNonRestArgs < len(parameterNames) && parameterNames[maxNonRestArgs] != "" {
restParameterName = parameterNames[maxNonRestArgs]
}
var questionToken *ast.QuestionToken
if maxNonRestArgs >= minArgumentCount {
questionToken = f.changeTracker.NodeFactory.NewToken(ast.KindQuestionToken)
}
parameters.Nodes = append(parameters.Nodes, f.changeTracker.NodeFactory.NewParameterDeclaration(
nil /*modifiers*/, f.changeTracker.NodeFactory.NewToken(ast.KindDotDotDotToken),
f.changeTracker.NodeFactory.NewIdentifier(restParameterName), questionToken,
f.changeTracker.NodeFactory.NewArrayTypeNode(f.changeTracker.NodeFactory.NewKeywordTypeNode(ast.KindUnknownKeyword)), nil, /*initializer*/
))
}
methodName := core.IfElse(name == nil, f.changeTracker.NodeFactory.NewIdentifier(""), createPropertyName(f.changeTracker.NodeFactory, name, quotePreference))
return f.changeTracker.NodeFactory.NewMethodDeclaration(
modifiers, nil /*asteriskToken*/, methodName, core.IfElse(optional, f.changeTracker.NodeFactory.NewToken(ast.KindQuestionToken), nil),
nil /*typeParameters*/, parameters, f.getReturnTypeFromSignatures(signatures, enclosingDeclaration, nodeBuilder, idToSymbol),
nil /*fullSignature*/, f.createBody(body, false /*ambient*/, quotePreference),
)
}
func (f *missingMemberFixer) getReturnTypeFromSignatures(signatures []*checker.Signature, enclosingDeclaration *ast.Node, nodeBuilder *checker.NodeBuilder, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
if len(signatures) == 0 {
return nil
}
returnTypes := make([]*checker.Type, 0, len(signatures))
for _, signature := range signatures {
returnTypes = append(returnTypes, f.typeChecker.GetReturnTypeOfSignature(signature))
}
unionType := f.typeChecker.GetUnionType(returnTypes)
return f.importTypeNode(nodeBuilder.TypeToTypeNode(unionType, enclosingDeclaration, nodebuilder.FlagsNoTruncation, nodebuilder.InternalFlagsAllowUnresolvedNames, nil /*typeArguments*/), idToSymbol)
}
func (f *missingMemberFixer) importTypeNode(typeNode *ast.TypeNode, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
if typeNode == nil || f.importAdder == nil {
return typeNode
}
importedTypeNode, symbols := autoimport.TryGetAutoImportableReferenceFromTypeNode(typeNode, idToSymbol)
if importedTypeNode != nil {
for _, symbol := range symbols {
f.importAdder.AddImportFromExportedSymbol(symbol, true /*isValidTypeOnlyUseSite*/)
}
return importedTypeNode
}
seen := make(map[*ast.Symbol]bool)
for _, symbol := range idToSymbol {
if symbol == nil || seen[symbol] {
continue
}
seen[symbol] = true
f.importAdder.AddImportFromExportedSymbol(symbol, true /*isValidTypeOnlyUseSite*/)
}
return typeNode
}
func (f *missingMemberFixer) createIndexSignatureDeclarationFromType(classDeclaration *ast.Node, implementedType *checker.Type, keyType *checker.Type) *ast.Node {
indexInfo := f.typeChecker.GetIndexInfoOfType(implementedType, keyType)
if indexInfo == nil {
return nil
}
builder := checker.NewNodeBuilder(f.typeChecker, f.changeTracker.EmitContext)
return builder.IndexInfoToIndexSignatureDeclaration(indexInfo, classDeclaration, nodebuilder.FlagsNone, nodebuilder.InternalFlagsNone, nil)
}
func (f *missingMemberFixer) createBody(body *ast.FunctionBody, ambient bool, quotePreference lsutil.QuotePreference) *ast.FunctionBody {
if ambient {
return nil
}
body = f.changeTracker.NodeFactory.DeepCloneNode(body)
if body == nil {
return f.createStubbedMethodBody(quotePreference)
}
return body
}
func (f *missingMemberFixer) createStubbedMethodBody(quotePreference lsutil.QuotePreference) *ast.FunctionBody {
tokenFlags := ast.TokenFlagsNone
if quotePreference == lsutil.QuotePreferenceSingle {
tokenFlags = ast.TokenFlagsSingleQuote
}
return f.changeTracker.NodeFactory.NewBlock(f.changeTracker.NodeFactory.NewNodeList([]*ast.Node{
f.changeTracker.NodeFactory.NewThrowStatement(
f.changeTracker.NodeFactory.NewNewExpression(
f.changeTracker.NodeFactory.NewIdentifier("Error"), nil /*typeArguments*/, f.changeTracker.NodeFactory.NewNodeList([]*ast.Node{
f.changeTracker.NodeFactory.NewStringLiteral(diagnostics.Method_not_implemented.Localize(f.locale), tokenFlags),
}),
),
),
}), true /*multiLine*/)
}
func createDummyParameters(factory *ast.NodeFactory, argCount int, names []string, types []*ast.TypeNode, minArgumentCount int, inJS bool) *ast.ParameterList {
parameters := make([]*ast.Node, 0, argCount)
parameterNameCounts := make(map[string]int)
for i := range argCount {
parameterName := ""
if i < len(names) && names[i] != "" {
parameterName = names[i]
} else {
parameterName = "arg" + strconv.Itoa(i)
}
count := parameterNameCounts[parameterName]
parameterNameCounts[parameterName] = count + 1
if count > 0 {
parameterName += strconv.Itoa(count)
}
var questionToken *ast.QuestionToken
if i >= minArgumentCount {
questionToken = factory.NewToken(ast.KindQuestionToken)
}
var typeNode *ast.TypeNode
if inJS {
typeNode = nil
} else if i < len(types) && types[i] != nil {
typeNode = types[i]
} else {
typeNode = factory.NewKeywordTypeNode(ast.KindUnknownKeyword)
}
parameters = append(parameters,
factory.NewParameterDeclaration(nil /*modifiers*/, nil /*dotDotDotToken*/, factory.NewIdentifier(parameterName), questionToken, typeNode, nil /*initializer*/))
}
return factory.NewNodeList(parameters)
}
func createDeclarationName(factory *ast.NodeFactory, typeChecker *checker.Checker, symbol *ast.Symbol, declaration *ast.Node) *ast.PropertyName {
if symbol != nil && symbol.CheckFlags&ast.CheckFlagsMapped != 0 {
nameType := typeChecker.GetNameTypeOfSymbol(symbol)
if nameType != nil && checker.IsTypeUsableAsPropertyName(nameType) {
return factory.NewIdentifier(checker.GetPropertyNameFromType(nameType))
}
}
if declaration != nil && declaration.Name() != nil {
return declaration.Name().Clone(factory)
}
if symbol != nil {
return factory.NewIdentifier(symbol.Name)
}
return nil
}
func createPropertyName(factory *ast.NodeFactory, node *ast.Node, quotePreference lsutil.QuotePreference) *ast.PropertyName {
if ast.IsIdentifier(node) && node.Text() == "constructor" {
tokenFlags := ast.TokenFlagsNone
if quotePreference == lsutil.QuotePreferenceSingle {
tokenFlags = ast.TokenFlagsSingleQuote
}
return factory.NewComputedPropertyName(factory.NewStringLiteral(node.Text(), tokenFlags))
}
return factory.DeepCloneNode(node)
}