|
| 1 | +namespace ts.codefix { |
| 2 | + const fixId = "convertTypedefToType"; |
| 3 | + registerCodeFix({ |
| 4 | + fixIds: [fixId], |
| 5 | + errorCodes: [Diagnostics.Convert_typedef_to_type.code], |
| 6 | + getCodeActions(context) { |
| 7 | + debugger; |
| 8 | + const checker = context.program.getTypeChecker(); |
| 9 | + const info = getInfo( |
| 10 | + context.sourceFile, |
| 11 | + context.span.start, |
| 12 | + checker |
| 13 | + ); |
| 14 | + const changes = textChanges.ChangeTracker.with(context, (t) => { |
| 15 | + const node = getTokenAtPosition( |
| 16 | + context.sourceFile, |
| 17 | + context.span.start |
| 18 | + ); |
| 19 | + |
| 20 | + if (containsTypeDefTag(node)) { |
| 21 | + fixSingleTypeDef(t, node, context, info?.typeNode); |
| 22 | + } |
| 23 | + }); |
| 24 | + |
| 25 | + if (changes.length > 0) { |
| 26 | + return [ |
| 27 | + createCodeFixAction( |
| 28 | + fixId, |
| 29 | + changes, |
| 30 | + Diagnostics.Convert_typedef_to_type, |
| 31 | + fixId, |
| 32 | + Diagnostics.Convert_all_typedefs_to_types |
| 33 | + ), |
| 34 | + ]; |
| 35 | + } |
| 36 | + }, |
| 37 | + getAllCodeActions() { |
| 38 | + debugger; |
| 39 | + return { changes: [] }; |
| 40 | + }, |
| 41 | + }); |
| 42 | + |
| 43 | + function fixSingleTypeDef( |
| 44 | + changes: textChanges.ChangeTracker, |
| 45 | + typeDefNode: JSDocTypedefTag | undefined, |
| 46 | + context: CodeFixContextBase, |
| 47 | + typeNode?: TypeNode |
| 48 | + ) { |
| 49 | + if (!typeDefNode?.name || !typeDefNode?.fullName || !typeNode) { |
| 50 | + return undefined; |
| 51 | + } |
| 52 | + |
| 53 | + const comment = typeDefNode.parent; |
| 54 | + |
| 55 | + changes.replaceNode( |
| 56 | + context.sourceFile, |
| 57 | + comment, |
| 58 | + factory.createTypeAliasDeclaration( |
| 59 | + [], |
| 60 | + typeDefNode.fullName.getFullText(), |
| 61 | + getTypeParameters(typeDefNode), |
| 62 | + typeNode |
| 63 | + ) |
| 64 | + // factory.createTypeParameterDeclaration() |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + function getInfo( |
| 69 | + sourceFile: SourceFile, |
| 70 | + pos: number, |
| 71 | + checker: TypeChecker |
| 72 | + ): { readonly typeNode: TypeNode; readonly type: Type } | undefined { |
| 73 | + const decl = findAncestor( |
| 74 | + getTokenAtPosition(sourceFile, pos), |
| 75 | + isTypeContainer |
| 76 | + ); |
| 77 | + const typeNode = decl && decl.type; |
| 78 | + return ( |
| 79 | + typeNode && { |
| 80 | + typeNode, |
| 81 | + type: checker.getTypeFromTypeNode(typeNode), |
| 82 | + } |
| 83 | + ); |
| 84 | + } |
| 85 | + |
| 86 | + type TypeContainer = |
| 87 | + | AsExpression |
| 88 | + | CallSignatureDeclaration |
| 89 | + | ConstructSignatureDeclaration |
| 90 | + | FunctionDeclaration |
| 91 | + | GetAccessorDeclaration |
| 92 | + | IndexSignatureDeclaration |
| 93 | + | MappedTypeNode |
| 94 | + | MethodDeclaration |
| 95 | + | MethodSignature |
| 96 | + | ParameterDeclaration |
| 97 | + | PropertyDeclaration |
| 98 | + | PropertySignature |
| 99 | + | SetAccessorDeclaration |
| 100 | + | TypeAliasDeclaration |
| 101 | + | TypeAssertion |
| 102 | + | VariableDeclaration; |
| 103 | + function isTypeContainer(node: Node): node is TypeContainer { |
| 104 | + // NOTE: Some locations are not handled yet: |
| 105 | + // MappedTypeNode.typeParameters and SignatureDeclaration.typeParameters, as well as CallExpression.typeArguments |
| 106 | + switch (node.kind) { |
| 107 | + case SyntaxKind.AsExpression: |
| 108 | + case SyntaxKind.CallSignature: |
| 109 | + case SyntaxKind.ConstructSignature: |
| 110 | + case SyntaxKind.FunctionDeclaration: |
| 111 | + case SyntaxKind.GetAccessor: |
| 112 | + case SyntaxKind.IndexSignature: |
| 113 | + case SyntaxKind.MappedType: |
| 114 | + case SyntaxKind.MethodDeclaration: |
| 115 | + case SyntaxKind.MethodSignature: |
| 116 | + case SyntaxKind.Parameter: |
| 117 | + case SyntaxKind.PropertyDeclaration: |
| 118 | + case SyntaxKind.PropertySignature: |
| 119 | + case SyntaxKind.SetAccessor: |
| 120 | + case SyntaxKind.TypeAliasDeclaration: |
| 121 | + case SyntaxKind.TypeAssertionExpression: |
| 122 | + case SyntaxKind.VariableDeclaration: |
| 123 | + return true; |
| 124 | + default: |
| 125 | + return false; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + function getTypeParameters(node: JSDocTypedefTag) { |
| 130 | + if (node.typeExpression && isJSDocTypeLiteral(node.typeExpression)) { |
| 131 | + node.typeExpression.jsDocPropertyTags?.map((tag) => { |
| 132 | + return factory.createTypeParameterDeclaration( |
| 133 | + [], |
| 134 | + tag.name.getFullText() |
| 135 | + ); |
| 136 | + }); |
| 137 | + } |
| 138 | + |
| 139 | + return []; |
| 140 | + } |
| 141 | + |
| 142 | + export function _containsJSDocTypedef(node: Node): node is HasJSDoc { |
| 143 | + if (hasJSDocNodes(node)) { |
| 144 | + const jsDocNodes = node.jsDoc || []; |
| 145 | + return jsDocNodes.some((node) => { |
| 146 | + const tags = node.tags || []; |
| 147 | + return tags.some((tag) => isJSDocTypedefTag(tag)); |
| 148 | + }); |
| 149 | + } |
| 150 | + return false; |
| 151 | + } |
| 152 | + |
| 153 | + export function getJSDocTypedefNode(node: HasJSDoc): JSDocTypedefTag { |
| 154 | + const jsDocNodes = node.jsDoc || []; |
| 155 | + |
| 156 | + return flatMap(jsDocNodes, (node) => { |
| 157 | + const tags = node.tags || []; |
| 158 | + return tags.filter((tag) => isJSDocTypedefTag(tag)); |
| 159 | + })[0] as unknown as JSDocTypedefTag; |
| 160 | + } |
| 161 | + |
| 162 | + export function containsTypeDefTag(node: Node): node is JSDocTypedefTag { |
| 163 | + return isInJSDoc(node) && isJSDocTypedefTag(node); |
| 164 | + } |
| 165 | +} |
0 commit comments