• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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