• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.Rename {
3    export function getRenameInfo(program: Program, sourceFile: SourceFile, position: number, options?: RenameInfoOptions): RenameInfo {
4        const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
5        if (nodeIsEligibleForRename(node)) {
6            const renameInfo = getRenameInfoForNode(node, program.getTypeChecker(), sourceFile, declaration => program.isSourceFileDefaultLibrary(declaration.getSourceFile()), options);
7            if (renameInfo) {
8                return renameInfo;
9            }
10        }
11        return getRenameInfoError(Diagnostics.You_cannot_rename_this_element);
12    }
13
14    function getRenameInfoForNode(node: Node, typeChecker: TypeChecker, sourceFile: SourceFile, isDefinedInLibraryFile: (declaration: Node) => boolean, options?: RenameInfoOptions): RenameInfo | undefined {
15        const symbol = typeChecker.getSymbolAtLocation(node);
16        if (!symbol) {
17            if (isStringLiteralLike(node)) {
18                const type = getContextualTypeOrAncestorTypeNodeType(node, typeChecker);
19                if (type && ((type.flags & TypeFlags.StringLiteral) || (
20                    (type.flags & TypeFlags.Union) && every((type as UnionType).types, type => !!(type.flags & TypeFlags.StringLiteral))
21                ))) {
22                    return getRenameInfoSuccess(node.text, node.text, ScriptElementKind.string, "", node, sourceFile);
23                }
24            }
25            else if (isLabelName(node)) {
26                const name = getTextOfNode(node);
27                return getRenameInfoSuccess(name, name, ScriptElementKind.label, ScriptElementKindModifier.none, node, sourceFile);
28            }
29            return undefined;
30        }
31        // Only allow a symbol to be renamed if it actually has at least one declaration.
32        const { declarations } = symbol;
33        if (!declarations || declarations.length === 0) return;
34
35        // Disallow rename for elements that are defined in the standard TypeScript library.
36        if (declarations.some(isDefinedInLibraryFile)) {
37            return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library);
38        }
39
40        // Cannot rename `default` as in `import { default as foo } from "./someModule";
41        if (isIdentifier(node) && node.originalKeywordKind === SyntaxKind.DefaultKeyword && symbol.parent && symbol.parent.flags & SymbolFlags.Module) {
42            return undefined;
43        }
44
45        if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) {
46            return options && options.allowRenameOfImportPath ? getRenameInfoForModule(node, sourceFile, symbol) : undefined;
47        }
48
49        const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
50        const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
51            ? stripQuotes(getTextOfIdentifierOrLiteral(node))
52            : undefined;
53        const displayName = specifierName || typeChecker.symbolToString(symbol);
54        const fullDisplayName = specifierName || typeChecker.getFullyQualifiedName(symbol);
55        return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(typeChecker,symbol), node, sourceFile);
56    }
57
58    function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined {
59        if (!isExternalModuleNameRelative(node.text)) {
60            return getRenameInfoError(Diagnostics.You_cannot_rename_a_module_via_a_global_import);
61        }
62
63        const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile);
64        if (!moduleSourceFile) return undefined;
65        const withoutIndex = endsWith(node.text, "/index") || endsWith(node.text, "/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index");
66        const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex;
67        const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory;
68        const indexAfterLastSlash = node.text.lastIndexOf("/") + 1;
69        // Span should only be the last component of the path. + 1 to account for the quote character.
70        const triggerSpan = createTextSpan(node.getStart(sourceFile) + 1 + indexAfterLastSlash, node.text.length - indexAfterLastSlash);
71        return {
72            canRename: true,
73            fileToRename: name,
74            kind,
75            displayName: name,
76            fullDisplayName: name,
77            kindModifiers: ScriptElementKindModifier.none,
78            triggerSpan,
79        };
80    }
81
82    function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfoSuccess {
83        return {
84            canRename: true,
85            fileToRename: undefined,
86            kind,
87            displayName,
88            fullDisplayName,
89            kindModifiers,
90            triggerSpan: createTriggerSpanForNode(node, sourceFile)
91        };
92    }
93
94    function getRenameInfoError(diagnostic: DiagnosticMessage): RenameInfoFailure {
95        return { canRename: false, localizedErrorMessage: getLocaleSpecificMessage(diagnostic) };
96    }
97
98    function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) {
99        let start = node.getStart(sourceFile);
100        let width = node.getWidth(sourceFile);
101        if (isStringLiteralLike(node)) {
102            // Exclude the quotes
103            start += 1;
104            width -= 2;
105        }
106        return createTextSpan(start, width);
107    }
108
109    function nodeIsEligibleForRename(node: Node): boolean {
110        switch (node.kind) {
111            case SyntaxKind.Identifier:
112            case SyntaxKind.PrivateIdentifier:
113            case SyntaxKind.StringLiteral:
114            case SyntaxKind.NoSubstitutionTemplateLiteral:
115            case SyntaxKind.ThisKeyword:
116                return true;
117            case SyntaxKind.NumericLiteral:
118                return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral);
119            default:
120                return false;
121        }
122    }
123}
124