diff --git a/src/services/services.ts b/src/services/services.ts index 670a83d50fc29..8d28d5d958a73 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -4891,8 +4891,8 @@ namespace ts { node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.ThisType || node.kind === SyntaxKind.SuperKeyword || - isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || - isNameOfExternalModuleImportOrDeclaration(node)) { + node.kind === SyntaxKind.StringLiteral || + isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) { const referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings*/ false, /*findInComments*/ false); return convertReferencedSymbols(referencedSymbols); @@ -5559,8 +5559,8 @@ namespace ts { // TODO (drosen): This should be enabled in a later release - currently breaks rename. // node.kind !== SyntaxKind.ThisKeyword && // node.kind !== SyntaxKind.SuperKeyword && - !isLiteralNameOfPropertyDeclarationOrIndexAccess(node) && - !isNameOfExternalModuleImportOrDeclaration(node)) { + node.kind !== SyntaxKind.StringLiteral && + !isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) { return undefined; } @@ -5595,6 +5595,10 @@ namespace ts { const symbol = typeChecker.getSymbolAtLocation(node); + if (!symbol && node.kind === SyntaxKind.StringLiteral) { + return getReferencesForStringLiteral(node, sourceFiles); + } + // Could not find a symbol e.g. unknown identifier if (!symbol) { // Can't have references to something that we have no symbol for. @@ -6151,6 +6155,52 @@ namespace ts { } } + + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[]): ReferencedSymbol[] { + const typeChecker = program.getTypeChecker(); + const type = getStringLiteralTypeForNode(node, typeChecker); + + if (!type) { + // nothing to do here. moving on + return undefined; + } + + const references: ReferenceEntry[] = []; + + for (const sourceFile of sourceFiles) { + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd()); + getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references); + } + + return [{ + definition: { + containerKind: "", + containerName: "", + fileName: node.getSourceFile().fileName, + kind: ScriptElementKind.variableElement, + name: type.text, + textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()) + }, + references: references + }]; + + function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void { + for (const position of possiblePositions) { + cancellationToken.throwIfCancellationRequested(); + + const node = getTouchingWord(sourceFile, position); + if (!node || node.kind !== SyntaxKind.StringLiteral) { + return; + } + + const type = getStringLiteralTypeForNode(node, typeChecker); + if (type === searchType) { + references.push(getReferenceEntryFromNode(node)); + } + } + } + } + function populateSearchSymbolSet(symbol: Symbol, location: Node): Symbol[] { // The search set contains at least the current symbol let result = [symbol]; @@ -7671,6 +7721,14 @@ namespace ts { } } + function getStringLiteralTypeForNode(node: StringLiteral | StringLiteralTypeNode, typeChecker: TypeChecker): StringLiteralType { + const searchNode = node.parent.kind === SyntaxKind.StringLiteralType ? node.parent : node; + const type = typeChecker.getTypeAtLocation(searchNode); + if (type && type.flags & TypeFlags.StringLiteral) { + return type; + } + return undefined; + } function getRenameInfo(fileName: string, position: number): RenameInfo { synchronizeHostData(); @@ -7678,46 +7736,60 @@ namespace ts { const sourceFile = getValidSourceFile(fileName); const typeChecker = program.getTypeChecker(); + const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings()); + const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName)); + const node = getTouchingWord(sourceFile, position); // Can only rename an identifier. - if (node && node.kind === SyntaxKind.Identifier) { - const symbol = typeChecker.getSymbolAtLocation(node); - - // Only allow a symbol to be renamed if it actually has at least one declaration. - if (symbol) { - const declarations = symbol.getDeclarations(); - if (declarations && declarations.length > 0) { - // Disallow rename for elements that are defined in the standard TypeScript library. - const defaultLibFileName = host.getDefaultLibFileName(host.getCompilationSettings()); - const canonicalDefaultLibName = getCanonicalFileName(ts.normalizePath(defaultLibFileName)); - if (defaultLibFileName) { - for (const current of declarations) { - const sourceFile = current.getSourceFile(); - // TODO (drosen): When is there no source file? - if (!sourceFile) { - continue; - } + if (node) { + if (node.kind === SyntaxKind.Identifier || + node.kind === SyntaxKind.StringLiteral || + isLiteralNameOfPropertyDeclarationOrIndexAccess(node)) { + const symbol = typeChecker.getSymbolAtLocation(node); + + // Only allow a symbol to be renamed if it actually has at least one declaration. + if (symbol) { + const declarations = symbol.getDeclarations(); + if (declarations && declarations.length > 0) { + // Disallow rename for elements that are defined in the standard TypeScript library. + if (forEach(declarations, isDefinedInLibraryFile)) { + return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library)); + } - const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName)); - if (canonicalName === canonicalDefaultLibName) { - return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library)); - } + const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); + const kind = getSymbolKind(symbol, node); + if (kind) { + return { + canRename: true, + kind, + displayName, + localizedErrorMessage: undefined, + fullDisplayName: typeChecker.getFullyQualifiedName(symbol), + kindModifiers: getSymbolModifiers(symbol), + triggerSpan: createTriggerSpanForNode(node, sourceFile) + }; } } - - const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); - const kind = getSymbolKind(symbol, node); - if (kind) { - return { - canRename: true, - kind, - displayName, - localizedErrorMessage: undefined, - fullDisplayName: typeChecker.getFullyQualifiedName(symbol), - kindModifiers: getSymbolModifiers(symbol), - triggerSpan: createTextSpan(node.getStart(), node.getWidth()) - }; + } + else if (node.kind === SyntaxKind.StringLiteral) { + const type = getStringLiteralTypeForNode(node, typeChecker); + if (type) { + if (isDefinedInLibraryFile(node)) { + return getRenameInfoError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library)); + } + else { + const displayName = stripQuotes(type.text); + return { + canRename: true, + kind: ScriptElementKind.variableElement, + displayName, + localizedErrorMessage: undefined, + fullDisplayName: displayName, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: createTriggerSpanForNode(node, sourceFile) + }; + } } } } @@ -7736,6 +7808,28 @@ namespace ts { triggerSpan: undefined }; } + + function isDefinedInLibraryFile(declaration: Node) { + if (defaultLibFileName) { + const sourceFile = declaration.getSourceFile(); + const canonicalName = getCanonicalFileName(ts.normalizePath(sourceFile.fileName)); + if (canonicalName === canonicalDefaultLibName) { + return true; + } + } + return false; + } + + function createTriggerSpanForNode(node: Node, sourceFile: SourceFile) { + let start = node.getStart(sourceFile); + let width = node.getWidth(sourceFile); + if (node.kind === SyntaxKind.StringLiteral) { + // Exclude the quotes + start += 1; + width -= 2; + } + return createTextSpan(start, width); + } } return { diff --git a/tests/cases/fourslash/findAllRefsForStringLiteralTypes.ts b/tests/cases/fourslash/findAllRefsForStringLiteralTypes.ts new file mode 100644 index 0000000000000..dcf7240312c1d --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForStringLiteralTypes.ts @@ -0,0 +1,14 @@ +/// + +////type Options = "[|option 1|]" | "option 2"; +////let myOption: Options = "[|option 1|]"; + +let ranges = test.ranges(); +for (let range of ranges) { + goTo.position(range.start); + + verify.referencesCountIs(ranges.length); + for (let expectedReference of ranges) { + verify.referencesAtPositionContains(expectedReference); + } +} \ No newline at end of file diff --git a/tests/cases/fourslash/getOccurrencesStringLiteralTypes.ts b/tests/cases/fourslash/getOccurrencesStringLiteralTypes.ts new file mode 100644 index 0000000000000..56af87497d5a4 --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesStringLiteralTypes.ts @@ -0,0 +1,13 @@ +/// + +////function foo(a: "[|option 1|]") { } +////foo("[|option 1|]"); + +const ranges = test.ranges(); +for (let r of ranges) { + goTo.position(r.start); + + for (let range of ranges) { + verify.occurrencesAtPositionContains(range, false); + } +} diff --git a/tests/cases/fourslash/getOccurrencesStringLiterals.ts b/tests/cases/fourslash/getOccurrencesStringLiterals.ts new file mode 100644 index 0000000000000..efa365cbbb8fa --- /dev/null +++ b/tests/cases/fourslash/getOccurrencesStringLiterals.ts @@ -0,0 +1,10 @@ +/// + +////var x = "[|string|]"; +////function f(a = "[|initial value|]") { } + +const ranges = test.ranges(); +for (let r of ranges) { + goTo.position(r.start); + verify.occurrencesAtPositionCount(0); +} diff --git a/tests/cases/fourslash/renameStingLiterals.ts b/tests/cases/fourslash/renameStingLiterals.ts new file mode 100644 index 0000000000000..0e1222f327ab9 --- /dev/null +++ b/tests/cases/fourslash/renameStingLiterals.ts @@ -0,0 +1,12 @@ +/// + +////var y: "string" = "string; +////var x = "/*1*/string"; +////function f(a = "/*2*/initial value") { } + + +goTo.marker("1"); +verify.renameInfoFailed(); + +goTo.marker("2"); +verify.renameInfoFailed(); diff --git a/tests/cases/fourslash/renameStingPropertyNames.ts b/tests/cases/fourslash/renameStingPropertyNames.ts new file mode 100644 index 0000000000000..a948220efadcf --- /dev/null +++ b/tests/cases/fourslash/renameStingPropertyNames.ts @@ -0,0 +1,20 @@ +/// + +////var o = { +//// [|prop|]: 0 +////}; +//// +////o = { +//// "[|prop|]": 1 +////}; +//// +////o["[|prop|]"]; +////o['[|prop|]']; +////o.[|prop|]; + + +let ranges = test.ranges(); +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +} diff --git a/tests/cases/fourslash/renameStringLiteralTypes.ts b/tests/cases/fourslash/renameStringLiteralTypes.ts new file mode 100644 index 0000000000000..f9df83371c13e --- /dev/null +++ b/tests/cases/fourslash/renameStringLiteralTypes.ts @@ -0,0 +1,18 @@ +/// + + +////interface AnimationOptions { +//// deltaX: number; +//// deltaY: number; +//// easing: "ease-in" | "ease-out" | "[|ease-in-out|]"; +////} +//// +////function animate(o: AnimationOptions) { } +//// +////animate({ deltaX: 100, deltaY: 100, easing: "[|ease-in-out|]" }); + +let ranges = test.ranges(); +for (let range of ranges) { + goTo.position(range.start); + verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +}