237 lines
9.1 KiB
Go
237 lines
9.1 KiB
Go
package ls
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/microsoft/typescript-go/internal/ast"
|
|
"github.com/microsoft/typescript-go/internal/astnav"
|
|
"github.com/microsoft/typescript-go/internal/checker"
|
|
"github.com/microsoft/typescript-go/internal/collections"
|
|
"github.com/microsoft/typescript-go/internal/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/lsp/lsproto"
|
|
"github.com/microsoft/typescript-go/internal/scanner"
|
|
)
|
|
|
|
const fixClassIncorrectlyImplementsInterfaceFixID = "fixClassIncorrectlyImplementsInterface"
|
|
|
|
var fixClassIncorrectlyImplementsInterfaceErrorCodes = []int32{
|
|
diagnostics.Class_0_incorrectly_implements_interface_1.Code(),
|
|
diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.Code(),
|
|
}
|
|
|
|
var FixClassIncorrectlyImplementsInterfaceProvider = &CodeFixProvider{
|
|
ErrorCodes: fixClassIncorrectlyImplementsInterfaceErrorCodes,
|
|
GetCodeActions: getCodeActionsToFixClassIncorrectlyImplementsInterface,
|
|
FixIds: []string{fixClassIncorrectlyImplementsInterfaceFixID},
|
|
GetAllCodeActions: getAllCodeActionsToFixClassIncorrectlyImplementsInterface,
|
|
}
|
|
|
|
func getCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Context, fixContext *CodeFixContext) ([]*CodeAction, error) {
|
|
classDeclaration := getClass(fixContext.SourceFile, fixContext.Span)
|
|
if classDeclaration == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
implementsTypes := ast.GetImplementsTypeNodes(classDeclaration)
|
|
locale := locale.FromContext(context)
|
|
|
|
typeChecker, done := fixContext.Program.GetTypeCheckerForFile(context, fixContext.SourceFile)
|
|
defer done()
|
|
|
|
var actions []*CodeAction
|
|
for _, implementedTypeNode := range implementsTypes {
|
|
changeTracker := change.NewTracker(context, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
|
|
importAdder, err := createImportAdder(context, fixContext, typeChecker)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addChanges(context, fixContext, changeTracker, importAdder, typeChecker, classDeclaration, implementedTypeNode)
|
|
changes := getChanges(changeTracker, importAdder, fixContext.SourceFile)
|
|
if len(changes) == 0 {
|
|
continue
|
|
}
|
|
|
|
actions = append(actions, &CodeAction{
|
|
Description: diagnostics.Implement_interface_0.Localize(locale, scanner.GetTextOfNode(implementedTypeNode)),
|
|
Changes: changes,
|
|
FixID: fixClassIncorrectlyImplementsInterfaceFixID,
|
|
FixAllDescription: diagnostics.Implement_all_unimplemented_interfaces.Localize(locale),
|
|
})
|
|
}
|
|
return actions, nil
|
|
}
|
|
|
|
func getAllCodeActionsToFixClassIncorrectlyImplementsInterface(context context.Context, fixContext *CodeFixContext) (*CombinedCodeActions, error) {
|
|
typeChecker, done := fixContext.Program.GetTypeCheckerForFile(context, fixContext.SourceFile)
|
|
defer done()
|
|
|
|
changeTracker := change.NewTracker(context, fixContext.Program.Options(), fixContext.LS.FormatOptions(), fixContext.LS.converters)
|
|
importAdder, err := createImportAdder(context, fixContext, typeChecker)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
seenClassDeclarations := collections.Set[*ast.Node]{}
|
|
|
|
for _, diag := range getAllDiagnostics(context, fixContext.Program, fixContext.SourceFile) {
|
|
if containsErrorCode(fixClassIncorrectlyImplementsInterfaceErrorCodes, diag.Code()) {
|
|
classDeclaration := getClass(fixContext.SourceFile, core.NewTextRange(diag.Pos(), diag.End()))
|
|
if classDeclaration == nil {
|
|
continue
|
|
}
|
|
if seenClassDeclarations.AddIfAbsent(classDeclaration) {
|
|
implementsTypes := ast.GetImplementsTypeNodes(classDeclaration)
|
|
for _, implementedTypeNode := range implementsTypes {
|
|
addChanges(context, fixContext, changeTracker, importAdder, typeChecker, classDeclaration, implementedTypeNode)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
changes := getChanges(changeTracker, importAdder, fixContext.SourceFile)
|
|
if len(changes) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return &CombinedCodeActions{
|
|
Description: diagnostics.Implement_all_unimplemented_interfaces.Localize(locale.FromContext(context)),
|
|
Changes: changes,
|
|
}, nil
|
|
}
|
|
|
|
func addChanges(context context.Context, fixContext *CodeFixContext, changeTracker *change.Tracker, importAdder autoimport.ImportAdder, typeChecker *checker.Checker, classDeclaration *ast.Node, implementedTypeNode *ast.Node) {
|
|
missingMemberFixer := newMissingMemberFixer(changeTracker, fixContext.Program, typeChecker, fixContext.LS.UserPreferences(), importAdder, locale.FromContext(context))
|
|
constructor := getConstructor(classDeclaration)
|
|
implementedType := typeChecker.GetTypeAtLocation(implementedTypeNode)
|
|
classType := typeChecker.GetTypeAtLocation(classDeclaration)
|
|
|
|
if typeChecker.GetNumberIndexType(classType) == nil {
|
|
member := missingMemberFixer.createIndexSignatureDeclarationFromType(classDeclaration, implementedType, typeChecker.GetNumberType())
|
|
if member != nil {
|
|
insertInterfaceMemberNode(changeTracker, fixContext.SourceFile, classDeclaration, constructor, member)
|
|
}
|
|
}
|
|
|
|
if typeChecker.GetStringIndexType(classType) == nil {
|
|
member := missingMemberFixer.createIndexSignatureDeclarationFromType(classDeclaration, implementedType, typeChecker.GetStringType())
|
|
if member != nil {
|
|
insertInterfaceMemberNode(changeTracker, fixContext.SourceFile, classDeclaration, constructor, member)
|
|
}
|
|
}
|
|
|
|
missingMembers := getMissingMembers(typeChecker, classDeclaration, []*checker.Type{implementedType})
|
|
for _, member := range missingMembers {
|
|
memberNodes := missingMemberFixer.createMemberFromSymbol(member, classDeclaration, fixContext.SourceFile, nil /*body*/, preserveOptionalFlagsAll)
|
|
for _, memberNode := range memberNodes {
|
|
insertInterfaceMemberNode(changeTracker, fixContext.SourceFile, classDeclaration, constructor, memberNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getChanges(changeTracker *change.Tracker, importAdder autoimport.ImportAdder, sourceFile *ast.SourceFile) []*lsproto.TextEdit {
|
|
fileChanges := changeTracker.GetChanges()[sourceFile.FileName()]
|
|
if importAdder != nil && importAdder.HasFixes() {
|
|
fileChanges = append(fileChanges, importAdder.Edits()...)
|
|
}
|
|
return fileChanges
|
|
}
|
|
|
|
func insertInterfaceMemberNode(changeTracker *change.Tracker, sourceFile *ast.SourceFile, classDeclaration *ast.Node, constructor *ast.Node, member *ast.Node) {
|
|
if constructor == nil {
|
|
changeTracker.InsertMemberAtStart(sourceFile, classDeclaration, member)
|
|
} else {
|
|
changeTracker.InsertNodeAfter(sourceFile, constructor, member)
|
|
}
|
|
}
|
|
|
|
func getClass(sourceFile *ast.SourceFile, span core.TextRange) *ast.Node {
|
|
token := astnav.GetTokenAtPosition(sourceFile, span.Pos())
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
return ast.GetContainingClass(token)
|
|
}
|
|
|
|
func getConstructor(classDeclaration *ast.Node) *ast.Node {
|
|
if classDeclaration == nil || classDeclaration.MemberList() == nil {
|
|
return nil
|
|
}
|
|
for _, member := range classDeclaration.MemberList().Nodes {
|
|
if member != nil && ast.IsConstructorDeclaration(member) {
|
|
return member
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getMissingMembers(typeChecker *checker.Checker, classDeclaration *ast.Node, implementedTypes []*checker.Type) []*ast.Symbol {
|
|
inheritedMembers := getInheritedMembers(typeChecker, classDeclaration)
|
|
seenMembers := make(map[string]*ast.Symbol)
|
|
|
|
var classMembers ast.SymbolTable
|
|
if classDeclaration.Symbol() != nil {
|
|
classMembers = classDeclaration.Symbol().Members
|
|
}
|
|
|
|
var missingMembers []*ast.Symbol
|
|
for _, implementedType := range implementedTypes {
|
|
for _, symbol := range typeChecker.GetPropertiesOfType(implementedType) {
|
|
if symbol == nil {
|
|
continue
|
|
}
|
|
if classMembers != nil && classMembers[symbol.Name] != nil {
|
|
continue
|
|
}
|
|
if inheritedMembers[symbol.Name] != nil || seenMembers[symbol.Name] != nil {
|
|
continue
|
|
}
|
|
flags := checker.GetDeclarationModifierFlagsFromSymbol(symbol)
|
|
if flags&ast.ModifierFlagsPrivate == 0 {
|
|
seenMembers[symbol.Name] = symbol
|
|
missingMembers = append(missingMembers, symbol)
|
|
}
|
|
}
|
|
}
|
|
return missingMembers
|
|
}
|
|
|
|
func getInheritedMembers(typeChecker *checker.Checker, classDeclaration *ast.Node) ast.SymbolTable {
|
|
typeNode := ast.GetClassExtendsHeritageElement(classDeclaration)
|
|
if typeNode == nil {
|
|
return ast.SymbolTable{}
|
|
}
|
|
|
|
baseType := typeChecker.GetTypeAtLocation(typeNode.AsNode())
|
|
if baseType == nil {
|
|
return ast.SymbolTable{}
|
|
}
|
|
|
|
inheritedMembers := make(ast.SymbolTable)
|
|
for _, symbol := range typeChecker.GetPropertiesOfType(baseType) {
|
|
if symbol == nil {
|
|
continue
|
|
}
|
|
flags := checker.GetDeclarationModifierFlagsFromSymbol(symbol)
|
|
if flags&ast.ModifierFlagsPrivate == 0 {
|
|
inheritedMembers[symbol.Name] = symbol
|
|
}
|
|
}
|
|
return inheritedMembers
|
|
}
|
|
|
|
func createImportAdder(context context.Context, fixContext *CodeFixContext, typeChecker *checker.Checker) (autoimport.ImportAdder, error) {
|
|
view, err := fixContext.LS.getPreparedAutoImportView(fixContext.SourceFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if view == nil {
|
|
return nil, nil
|
|
}
|
|
return autoimport.NewImportAdder(context, fixContext.Program, typeChecker, fixContext.SourceFile, view, fixContext.LS.FormatOptions(), fixContext.LS.converters, fixContext.LS.UserPreferences()), nil
|
|
}
|