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