1/* @internal */ 2namespace ts.codefix { 3 const fixId = "fixUnreferenceableDecoratorMetadata"; 4 const errorCodes = [Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled.code]; 5 registerCodeFix({ 6 errorCodes, 7 getCodeActions: context => { 8 const importDeclaration = getImportDeclaration(context.sourceFile, context.program, context.span.start); 9 if (!importDeclaration) return; 10 11 const namespaceChanges = textChanges.ChangeTracker.with(context, t => importDeclaration.kind === SyntaxKind.ImportSpecifier && doNamespaceImportChange(t, context.sourceFile, importDeclaration, context.program)); 12 const typeOnlyChanges = textChanges.ChangeTracker.with(context, t => doTypeOnlyImportChange(t, context.sourceFile, importDeclaration, context.program)); 13 let actions: CodeFixAction[] | undefined; 14 if (namespaceChanges.length) { 15 actions = append(actions, createCodeFixActionWithoutFixAll(fixId, namespaceChanges, Diagnostics.Convert_named_imports_to_namespace_import)); 16 } 17 if (typeOnlyChanges.length) { 18 actions = append(actions, createCodeFixActionWithoutFixAll(fixId, typeOnlyChanges, Diagnostics.Convert_to_type_only_import)); 19 } 20 return actions; 21 }, 22 fixIds: [fixId], 23 }); 24 25 function getImportDeclaration(sourceFile: SourceFile, program: Program, start: number): ImportClause | ImportSpecifier | ImportEqualsDeclaration | undefined { 26 const identifier = tryCast(getTokenAtPosition(sourceFile, start), isIdentifier); 27 if (!identifier || identifier.parent.kind !== SyntaxKind.TypeReference) return; 28 29 const checker = program.getTypeChecker(); 30 const symbol = checker.getSymbolAtLocation(identifier); 31 return find(symbol?.declarations || emptyArray, or(isImportClause, isImportSpecifier, isImportEqualsDeclaration) as (n: Node) => n is ImportClause | ImportSpecifier | ImportEqualsDeclaration); 32 } 33 34 // Converts the import declaration of the offending import to a type-only import, 35 // only if it can be done without affecting other imported names. If the conversion 36 // cannot be done cleanly, we could offer to *extract* the offending import to a 37 // new type-only import declaration, but honestly I doubt anyone will ever use this 38 // codefix at all, so it's probably not worth the lines of code. 39 function doTypeOnlyImportChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDeclaration: ImportClause | ImportSpecifier | ImportEqualsDeclaration, program: Program) { 40 if (importDeclaration.kind === SyntaxKind.ImportEqualsDeclaration) { 41 changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, importDeclaration.name); 42 return; 43 } 44 45 const importClause = importDeclaration.kind === SyntaxKind.ImportClause ? importDeclaration : importDeclaration.parent.parent; 46 if (importClause.name && importClause.namedBindings) { 47 // Cannot convert an import with a default import and named bindings to type-only 48 // (it's a grammar error). 49 return; 50 } 51 52 const checker = program.getTypeChecker(); 53 const importsValue = !!forEachImportClauseDeclaration(importClause, decl => { 54 if (skipAlias(decl.symbol, checker).flags & SymbolFlags.Value) return true; 55 }); 56 57 if (importsValue) { 58 // Assume that if someone wrote a non-type-only import that includes some values, 59 // they intend to use those values in value positions, even if they haven't yet. 60 // Don't convert it to type-only. 61 return; 62 } 63 64 changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, importClause); 65 } 66 67 function doNamespaceImportChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDeclaration: ImportSpecifier, program: Program) { 68 refactor.doChangeNamedToNamespaceOrDefault(sourceFile, program, changes, importDeclaration.parent); 69 } 70} 71