diff --git a/src/harness/client.ts b/src/harness/client.ts index d873cd9b4fefc..f8841eaa6b027 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -390,8 +390,8 @@ namespace ts.server { const locations: RenameLocation[] = []; for (const entry of body.locs) { const fileName = entry.file; - for (const loc of entry.locs) { - locations.push({ textSpan: this.decodeSpan(loc, fileName), fileName }); + for (const { start, end, ...prefixSuffixText } of entry.locs) { + locations.push({ textSpan: this.decodeSpan({ start, end }, fileName), fileName, ...prefixSuffixText }); } } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index c06e95339f106..b7ce7437f3c4b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1395,7 +1395,7 @@ Actual: ${stringify(fullActual)}`); } } - public verifyRenameLocations(startRanges: ArrayOrSingle, options: ReadonlyArray | { findInStrings?: boolean, findInComments?: boolean, ranges: ReadonlyArray }) { + public verifyRenameLocations(startRanges: ArrayOrSingle, options: FourSlashInterface.RenameLocationsOptions) { const { findInStrings = false, findInComments = false, ranges = this.getRanges() } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options } : options; for (const startRange of toArray(startRanges)) { @@ -1412,7 +1412,10 @@ Actual: ${stringify(fullActual)}`); const sort = (locations: ReadonlyArray | undefined) => locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start); - assert.deepEqual(sort(references), sort(ranges.map((r): ts.RenameLocation => ({ fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r) })))); + assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => { + const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; + return { fileName: range.fileName, textSpan: ts.createTextSpanFromRange(range), ...prefixSuffixText }; + }))); } } @@ -4484,7 +4487,7 @@ namespace FourSlashInterface { this.state.verifyRenameInfoFailed(message); } - public renameLocations(startRanges: ArrayOrSingle, options: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: FourSlash.Range[] }) { + public renameLocations(startRanges: ArrayOrSingle, options: RenameLocationsOptions) { this.state.verifyRenameLocations(startRanges, options); } @@ -4959,4 +4962,11 @@ namespace FourSlashInterface { readonly newFileContents: { readonly [fileName: string]: string }; readonly preferences?: ts.UserPreferences; } + + export type RenameLocationsOptions = ReadonlyArray | { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + readonly ranges: ReadonlyArray; + }; + export type RenameLocationOptions = FourSlash.Range | { readonly range: FourSlash.Range, readonly prefixText?: string, readonly suffixText?: string }; } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 68c855eecca34..52389c9e0d65d 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1135,7 +1135,12 @@ namespace ts.server.protocol { /** The file to which the spans apply */ file: string; /** The text spans in this group */ - locs: TextSpan[]; + locs: RenameTextSpan[]; + } + + export interface RenameTextSpan extends TextSpan { + readonly prefixText?: string; + readonly suffixText?: string; } export interface RenameResponseBody { diff --git a/src/server/session.ts b/src/server/session.ts index dddf705e55c9f..744cfe0492732 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1190,10 +1190,11 @@ namespace ts.server { private toSpanGroups(locations: ReadonlyArray): ReadonlyArray { const map = createMap(); - for (const { fileName, textSpan } of locations) { + for (const { fileName, textSpan, originalTextSpan: _, originalFileName: _1, ...prefixSuffixText } of locations) { let group = map.get(fileName); if (!group) map.set(fileName, group = { file: fileName, locs: [] }); - group.locs.push(this.toLocationTextSpan(textSpan, Debug.assertDefined(this.projectService.getScriptInfo(fileName)))); + const scriptInfo = Debug.assertDefined(this.projectService.getScriptInfo(fileName)); + group.locs.push({ ...this.toLocationTextSpan(textSpan, scriptInfo), ...prefixSuffixText }); } return arrayFrom(map.values()); } diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index f924208e321de..ed8939079e3ec 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -196,7 +196,7 @@ namespace ts.codefix { function getReferences(token: PropertyName | Token, program: Program, cancellationToken: CancellationToken): ReadonlyArray { // Position shouldn't matter since token is not a SourceFile. return mapDefined(FindAllReferences.getReferenceEntriesForNode(-1, token, program, program.getSourceFiles(), cancellationToken), entry => - entry.type === "node" ? tryCast(entry.node, isIdentifier) : undefined); + entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined); } function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type | undefined { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index b857a05dffbc5..9fde734049c3f 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -13,19 +13,20 @@ namespace ts.FindAllReferences { | { readonly type: DefinitionKind.This; readonly node: Node } | { readonly type: DefinitionKind.String; readonly node: StringLiteral }; + export const enum EntryKind { Span, Node, StringLiteral, SearchedLocalFoundProperty, SearchedPropertyFoundLocal } + export type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; export type Entry = NodeEntry | SpanEntry; export interface NodeEntry { - type: "node"; - node: Node; - isInString?: true; + readonly kind: NodeEntryKind; + readonly node: Node; } export interface SpanEntry { - type: "span"; - fileName: string; - textSpan: TextSpan; + readonly kind: EntryKind.Span; + readonly fileName: string; + readonly textSpan: TextSpan; } - export function nodeEntry(node: Node, isInString?: true): NodeEntry { - return { type: "node", node: (node as NamedDeclaration).name || node, isInString }; + export function nodeEntry(node: Node, kind: NodeEntryKind = EntryKind.Node): NodeEntry { + return { kind, node: (node as NamedDeclaration).name || node }; } export interface Options { @@ -33,7 +34,7 @@ namespace ts.FindAllReferences { readonly findInComments?: boolean; /** * True if we are renaming the symbol. - * If so, we will find fewer references -- if it is referenced by several different names, we sill only find references for the original name. + * If so, we will find fewer references -- if it is referenced by several different names, we still only find references for the original name. */ readonly isForRename?: boolean; /** True if we are searching for implementations. We will have a different method of adding references if so. */ @@ -84,10 +85,15 @@ namespace ts.FindAllReferences { } } - export function findReferencedEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, node: Node, position: number, options: Options | undefined): ReferenceEntry[] | undefined { - return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), toReferenceEntry); + export function findReferenceOrRenameEntries( + program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray, node: Node, position: number, options: Options | undefined, + convertEntry: ToReferenceOrRenameEntry, + ): T[] | undefined { + return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), entry => convertEntry(entry, node)); } + export type ToReferenceOrRenameEntry = (entry: Entry, originalNode: Node) => T; + export function getReferenceEntriesForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray, cancellationToken: CancellationToken, options: Options = {}, sourceFilesSet: ReadonlyMap = arrayToSet(sourceFiles, f => f.fileName)): Entry[] | undefined { return flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options, sourceFilesSet)); } @@ -143,26 +149,64 @@ namespace ts.FindAllReferences { return { displayParts, kind: symbolKind }; } - function toReferenceEntry(entry: Entry): ReferenceEntry { - if (entry.type === "span") { - return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false }; - } + export function toRenameLocation(entry: Entry, originalNode: Node): RenameLocation { + return { ...entryToDocumentSpan(entry), ...getPrefixAndSuffixText(entry, originalNode) }; + } - const { node, isInString } = entry; - const sourceFile = node.getSourceFile(); + export function toReferenceEntry(entry: Entry): ReferenceEntry { + const { textSpan, fileName } = entryToDocumentSpan(entry); + if (entry.kind === EntryKind.Span) { + return { textSpan, fileName, isWriteAccess: false, isDefinition: false }; + } + const { kind, node } = entry; return { - fileName: sourceFile.fileName, - textSpan: getTextSpan(node, sourceFile), + textSpan, + fileName, isWriteAccess: isWriteAccessForReference(node), isDefinition: node.kind === SyntaxKind.DefaultKeyword || !!getDeclarationFromName(node) || isLiteralComputedPropertyDeclarationName(node), - isInString, + isInString: kind === EntryKind.StringLiteral ? true : undefined, }; } + function entryToDocumentSpan(entry: Entry): DocumentSpan { + if (entry.kind === EntryKind.Span) { + return { textSpan: entry.textSpan, fileName: entry.fileName }; + } + else { + const sourceFile = entry.node.getSourceFile(); + return { textSpan: getTextSpan(entry.node, sourceFile), fileName: sourceFile.fileName }; + } + } + + function getPrefixAndSuffixText(entry: Entry, originalNode: Node): { readonly prefixText?: string, readonly suffixText?: string } { + if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) { + const { node, kind } = entry; + const name = originalNode.text; + const isShorthandAssignment = isShorthandPropertyAssignment(node.parent); + if (isShorthandAssignment || isObjectBindingElementWithoutPropertyName(node.parent)) { + if (kind === EntryKind.SearchedLocalFoundProperty) { + return { prefixText: name + ": " }; + } + else if (kind === EntryKind.SearchedPropertyFoundLocal) { + return { suffixText: ": " + name }; + } + else { + return isShorthandAssignment + // In `const o = { x }; o.x`, symbolAtLocation at `x` in `{ x }` is the property symbol. + ? { suffixText: ": " + name } + // For a binding element `const { x } = o;`, symbolAtLocation at `x` is the property symbol. + : { prefixText: name + ": " }; + } + } + } + + return emptyOptions; + } + function toImplementationLocation(entry: Entry, checker: TypeChecker): ImplementationLocation { - if (entry.type === "node") { + if (entry.kind !== EntryKind.Span) { const { node } = entry; const sourceFile = node.getSourceFile(); return { textSpan: getTextSpan(node, sourceFile), fileName: sourceFile.fileName, ...implementationKindDisplayParts(node, checker) }; @@ -196,18 +240,18 @@ namespace ts.FindAllReferences { } export function toHighlightSpan(entry: Entry): { fileName: string, span: HighlightSpan } { - if (entry.type === "span") { + if (entry.kind === EntryKind.Span) { const { fileName, textSpan } = entry; return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } }; } - const { node, isInString } = entry; + const { node, kind } = entry; const sourceFile = node.getSourceFile(); const writeAccess = isWriteAccessForReference(node); const span: HighlightSpan = { textSpan: getTextSpan(node, sourceFile), kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, - isInString + isInString: kind === EntryKind.StringLiteral ? true : undefined, }; return { fileName: sourceFile.fileName, span }; } @@ -348,11 +392,11 @@ namespace ts.FindAllReferences.Core { } } // import("foo") with no qualifier will reference the `export =` of the module, which may be referenced anyway. - return { type: "node", node: reference.literal }; + return nodeEntry(reference.literal); } else { return { - type: "span", + kind: EntryKind.Span, fileName: reference.referencingFile.fileName, textSpan: createTextSpanFromRange(reference.ref), }; @@ -366,7 +410,7 @@ namespace ts.FindAllReferences.Core { break; case SyntaxKind.ModuleDeclaration: if (sourceFilesSet.has(decl.getSourceFile().fileName)) { - references.push({ type: "node", node: (decl as ModuleDeclaration).name }); + references.push(nodeEntry((decl as ModuleDeclaration).name)); } break; default: @@ -422,7 +466,7 @@ namespace ts.FindAllReferences.Core { searchForImportsOfExport(node, symbol, { exportingModuleSymbol: Debug.assertDefined(symbol.parent, "Expected export symbol to have a parent"), exportKind: ExportKind.Default }, state); } else { - const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.implementations) : [symbol] }); + const search = state.createSearch(node, symbol, /*comingFrom*/ undefined, { allSearchSymbols: node ? populateSearchSymbolSet(symbol, node, checker, !!options.isForRename, !!options.implementations) : [symbol] }); // Try to get the smallest valid scope that we can limit our search to; // otherwise we'll need to search globally (i.e. include each file). @@ -581,21 +625,21 @@ namespace ts.FindAllReferences.Core { * Callback to add references for a particular searched symbol. * This initializes a reference group, so only call this if you will add at least one reference. */ - referenceAdder(searchSymbol: Symbol): (node: Node) => void { + referenceAdder(searchSymbol: Symbol): (node: Node, kind?: NodeEntryKind) => void { const symbolId = getSymbolId(searchSymbol); let references = this.symbolIdToReferences[symbolId]; if (!references) { references = this.symbolIdToReferences[symbolId] = []; this.result.push({ definition: { type: DefinitionKind.Symbol, symbol: searchSymbol }, references }); } - return node => references.push(nodeEntry(node)); + return (node, kind) => references.push(nodeEntry(node, kind)); } /** Add a reference with no associated definition. */ addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { this.result.push({ definition: undefined, - references: [{ type: "span", fileName, textSpan }] + references: [{ kind: EntryKind.Span, fileName, textSpan }] }); } @@ -707,21 +751,6 @@ namespace ts.FindAllReferences.Core { : undefined; } - function getObjectBindingElementWithoutPropertyName(symbol: Symbol): BindingElement & { name: Identifier } | undefined { - const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); - if (bindingElement && - bindingElement.parent.kind === SyntaxKind.ObjectBindingPattern && - isIdentifier(bindingElement.name) && - !bindingElement.propertyName) { - return bindingElement as BindingElement & { name: Identifier }; - } - } - - function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { - const bindingElement = getObjectBindingElementWithoutPropertyName(symbol); - return bindingElement && getPropertySymbolFromBindingElement(checker, bindingElement); - } - /** * Determines the smallest scope in which a symbol may have named references. * Note that not every construct has been accounted for. This function can @@ -754,7 +783,7 @@ namespace ts.FindAllReferences.Core { // If symbol is of object binding pattern element without property name we would want to // look for property too and that could be anywhere - if (getObjectBindingElementWithoutPropertyName(symbol)) { + if (declarations.some(isObjectBindingElementWithoutPropertyName)) { return undefined; } @@ -1099,13 +1128,14 @@ namespace ts.FindAllReferences.Core { } } - function addReference(referenceLocation: Node, relatedSymbol: Symbol, state: State): void { - const addRef = state.referenceAdder(relatedSymbol); + function addReference(referenceLocation: Node, relatedSymbol: Symbol | RelatedSymbol, state: State): void { + const { kind, symbol } = "kind" in relatedSymbol ? relatedSymbol : { kind: undefined, symbol: relatedSymbol }; + const addRef = state.referenceAdder(symbol); if (state.options.implementations) { addImplementationReferences(referenceLocation, addRef, state); } else { - addRef(referenceLocation); + addRef(referenceLocation, kind); } } @@ -1428,7 +1458,7 @@ namespace ts.FindAllReferences.Core { const references = flatMap(sourceFiles, sourceFile => { cancellationToken.throwIfCancellationRequested(); return mapDefined(getPossibleSymbolReferenceNodes(sourceFile, node.text), ref => - isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, /*isInString*/ true) : undefined); + isStringLiteral(ref) && ref.text === node.text ? nodeEntry(ref, EntryKind.StringLiteral) : undefined); }); return [{ @@ -1439,48 +1469,55 @@ namespace ts.FindAllReferences.Core { // For certain symbol kinds, we need to include other symbols in the search set. // This is not needed when searching for re-exports. - function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, implementations: boolean): Symbol[] { + function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, isForRename: boolean, implementations: boolean): Symbol[] { const result: Symbol[] = []; - forEachRelatedSymbol(symbol, location, checker, + forEachRelatedSymbol(symbol, location, checker, isForRename, (sym, root, base) => { result.push(base || root || sym); }, /*allowBaseTypes*/ () => !implementations); return result; } function forEachRelatedSymbol( - symbol: Symbol, location: Node, checker: TypeChecker, - cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol) => T | undefined, + symbol: Symbol, location: Node, checker: TypeChecker, isForRenamePopulateSearchSymbolSet: boolean, + cbSymbol: (symbol: Symbol, rootSymbol?: Symbol, baseSymbol?: Symbol, kind?: NodeEntryKind) => T | undefined, allowBaseTypes: (rootSymbol: Symbol) => boolean, ): T | undefined { const containingObjectLiteralElement = getContainingObjectLiteralElement(location); if (containingObjectLiteralElement) { + /* Because in short-hand property assignment, location has two meaning : property name and as value of the property + * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of + * property name and variable declaration of the identifier. + * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service + * should show both 'name' in 'obj' and 'name' in variable declaration + * const name = "Foo"; + * const obj = { name }; + * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment + * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration + * will be included correctly. + */ + const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); // gets the local symbol + if (shorthandValueSymbol && isForRenamePopulateSearchSymbolSet) { + // When renaming 'x' in `const o = { x }`, just rename the local variable, not the property. + return cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); + } + // If the location is in a context sensitive location (i.e. in an object literal) try // to get a contextual type for it, and add the property symbol from the contextual // type to the search set const contextualType = checker.getContextualType(containingObjectLiteralElement.parent); - const res = contextualType && firstDefined(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), fromRoot); + const res = contextualType && firstDefined( + getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker, contextualType, /*unionSymbolOk*/ true), + sym => fromRoot(sym, EntryKind.SearchedPropertyFoundLocal)); if (res) return res; // If the location is name of property symbol from object literal destructuring pattern // Search the property symbol // for ( { property: p2 } of elems) { } const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); - const res1 = propertySymbol && cbSymbol(propertySymbol); + const res1 = propertySymbol && cbSymbol(propertySymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedPropertyFoundLocal); if (res1) return res1; - /* Because in short-hand property assignment, location has two meaning : property name and as value of the property - * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of - * property name and variable declaration of the identifier. - * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service - * should show both 'name' in 'obj' and 'name' in variable declaration - * const name = "Foo"; - * const obj = { name }; - * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment - * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration - * will be included correctly. - */ - const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); - const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol); + const res2 = shorthandValueSymbol && cbSymbol(shorthandValueSymbol, /*rootSymbol*/ undefined, /*baseSymbol*/ undefined, EntryKind.SearchedLocalFoundProperty); if (res2) return res2; } @@ -1494,12 +1531,14 @@ namespace ts.FindAllReferences.Core { return fromRoot(symbol.flags & SymbolFlags.FunctionScopedVariable ? paramProps[1] : paramProps[0]); } - // If this is symbol of binding element without propertyName declaration in Object binding pattern - // Include the property in the search - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); - return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol); + // symbolAtLocation for a binding element is the local symbol. See if the search symbol is the property. + // Don't do this when populating search set for a rename -- just rename the local. + if (!isForRenamePopulateSearchSymbolSet) { + const bindingElementPropertySymbol = isObjectBindingElementWithoutPropertyName(location.parent) ? getPropertySymbolFromBindingElement(checker, location.parent) : undefined; + return bindingElementPropertySymbol && fromRoot(bindingElementPropertySymbol, EntryKind.SearchedPropertyFoundLocal); + } - function fromRoot(sym: Symbol): T | undefined { + function fromRoot(sym: Symbol, kind?: NodeEntryKind): T | undefined { // If this is a union property: // - In populateSearchSymbolsSet we will add all the symbols from all its source symbols in all unioned types. // - In findRelatedSymbol, we will just use the union symbol if any source symbol is included in the search. @@ -1507,20 +1546,24 @@ namespace ts.FindAllReferences.Core { // - In populateSearchSymbolsSet, add the root the list // - In findRelatedSymbol, return the source symbol if that is in the search. (Do not return the instantiation symbol.) return firstDefined(checker.getRootSymbols(sym), rootSymbol => - cbSymbol(sym, rootSymbol) + cbSymbol(sym, rootSymbol, /*baseSymbol*/ undefined, kind) // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions || (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && allowBaseTypes(rootSymbol) - ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base)) + ? getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.name, checker, base => cbSymbol(sym, rootSymbol, base, kind)) : undefined)); } } - function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): Symbol | undefined { + interface RelatedSymbol { + readonly symbol: Symbol; + readonly kind: NodeEntryKind | undefined; + } + function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): RelatedSymbol | undefined { const { checker } = state; - return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, - (sym, rootSymbol, baseSymbol) => search.includes(baseSymbol || rootSymbol || sym) + return forEachRelatedSymbol(referenceSymbol, referenceLocation, checker, /*isForRenamePopulateSearchSymbolSet*/ false, + (sym, rootSymbol, baseSymbol, kind): RelatedSymbol | undefined => search.includes(baseSymbol || rootSymbol || sym) // For a base type, use the symbol for the derived type. For a synthetic (e.g. union) property, use the union symbol. - ? rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym + ? { symbol: rootSymbol && !(getCheckFlags(sym) & CheckFlags.Synthetic) ? rootSymbol : sym, kind } : undefined, /*allowBaseTypes*/ rootSymbol => !(search.parents && !search.parents.some(parent => explicitlyInheritsFrom(rootSymbol.parent!, parent, state.inheritsFromCache, checker)))); diff --git a/src/services/refactors/generateGetAccessorAndSetAccessor.ts b/src/services/refactors/generateGetAccessorAndSetAccessor.ts index 57b25445f5de0..96b8b1e8b06e6 100644 --- a/src/services/refactors/generateGetAccessorAndSetAccessor.ts +++ b/src/services/refactors/generateGetAccessorAndSetAccessor.ts @@ -226,7 +226,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor { const { file, program, cancellationToken } = context; const referenceEntries = mapDefined(FindAllReferences.getReferenceEntriesForNode(originalName.parent.pos, originalName, program, [file], cancellationToken!), entry => // TODO: GH#18217 - (entry.type === "node" && rangeContainsRange(constructor, entry.node) && isIdentifier(entry.node) && isWriteAccess(entry.node)) ? entry.node : undefined); + (entry.kind !== FindAllReferences.EntryKind.Span && rangeContainsRange(constructor, entry.node) && isIdentifier(entry.node) && isWriteAccess(entry.node)) ? entry.node : undefined); forEach(referenceEntries, entry => { const parent = entry.parent; diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index 8b6af7148925f..693cdd9c6cb3d 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -158,7 +158,7 @@ namespace ts.refactor { const shouldMove = (name: Identifier): boolean => { const symbol = isBindingElement(name.parent) - ? getPropertySymbolFromBindingElement(checker, name.parent as BindingElement & { name: Identifier }) + ? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName) : skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217 return !!symbol && movedSymbols.has(symbol); }; diff --git a/src/services/services.ts b/src/services/services.ts index 542cc86987325..9e08f1b14a6c8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1544,20 +1544,20 @@ namespace ts { const node = getTouchingPropertyName(sourceFile, position); if (isIdentifier(node) && isJsxOpeningElement(node.parent) || isJsxClosingElement(node.parent)) { const { openingElement, closingElement } = node.parent.parent; - return [openingElement, closingElement].map((node): RenameLocation => ({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) })); + return [openingElement, closingElement].map((node): RenameLocation => + ({ fileName: sourceFile.fileName, textSpan: createTextSpanFromNode(node.tagName, sourceFile) })); } else { - const refs = getReferences(node, position, { findInStrings, findInComments, isForRename: true }); - return refs && refs.map(({ fileName, textSpan }): RenameLocation => ({ fileName, textSpan })); + return getReferencesWorker(node, position, { findInStrings, findInComments, isForRename: true }, FindAllReferences.toRenameLocation); } } function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined { synchronizeHostData(); - return getReferences(getTouchingPropertyName(getValidSourceFile(fileName), position), position); + return getReferencesWorker(getTouchingPropertyName(getValidSourceFile(fileName), position), position, {}, FindAllReferences.toReferenceEntry); } - function getReferences(node: Node, position: number, options?: FindAllReferences.Options): ReferenceEntry[] | undefined { + function getReferencesWorker(node: Node, position: number, options: FindAllReferences.Options, cb: FindAllReferences.ToReferenceOrRenameEntry): T[] | undefined { synchronizeHostData(); // Exclude default library when renaming as commonly user don't want to change that file. @@ -1565,7 +1565,7 @@ namespace ts { ? program.getSourceFiles().filter(sourceFile => !program.isSourceFileDefaultLibrary(sourceFile)) : program.getSourceFiles(); - return FindAllReferences.findReferencedEntries(program, cancellationToken, sourceFiles, node, position, options); + return FindAllReferences.findReferenceOrRenameEntries(program, cancellationToken, sourceFiles, node, position, options, cb); } function findReferences(fileName: string, position: number): ReferencedSymbol[] | undefined { diff --git a/src/services/types.ts b/src/services/types.ts index dbf79cea78381..ff2081d3c2244 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -619,6 +619,8 @@ namespace ts { } export interface RenameLocation extends DocumentSpan { + readonly prefixText?: string; + readonly suffixText?: string; } export interface ReferenceEntry extends DocumentSpan { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a2eb2f0270c53..427c31e53845e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1325,7 +1325,16 @@ namespace ts { }); } - export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: BindingElement & { name: Identifier }) { + export type ObjectBindingElementWithoutPropertyName = BindingElement & { name: Identifier }; + + export function isObjectBindingElementWithoutPropertyName(bindingElement: Node): bindingElement is ObjectBindingElementWithoutPropertyName { + return isBindingElement(bindingElement) && + isObjectBindingPattern(bindingElement.parent) && + isIdentifier(bindingElement.name) && + !bindingElement.propertyName; + } + + export function getPropertySymbolFromBindingElement(checker: TypeChecker, bindingElement: ObjectBindingElementWithoutPropertyName) { const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); const propSymbol = typeOfPattern && checker.getPropertyOfType(typeOfPattern, bindingElement.name.text); if (propSymbol && propSymbol.flags & SymbolFlags.Accessor) { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index cb1b7f3983d6a..ad23e4a832356 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -449,6 +449,14 @@ namespace ts.projectSystem { const toLocation = protocolToLocation(str); return { start: toLocation(span.start), end: toLocation(textSpanEnd(span)) }; } + function protocolRenameSpanFromSubstring( + str: string, + substring: string, + options?: SpanFromSubstringOptions, + prefixSuffixText?: { readonly prefixText?: string, readonly suffixText?: string }, + ): protocol.RenameTextSpan { + return { ...protocolTextSpanFromSubstring(str, substring, options), ...prefixSuffixText }; + } function textSpanFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): TextSpan { const start = nthIndexOf(str, substring, options ? options.index : 0); Debug.assert(start !== -1); @@ -463,6 +471,9 @@ namespace ts.projectSystem { function documentSpanFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): DocumentSpan { return { fileName: file.path, textSpan: textSpanFromSubstring(file.content, substring, options) }; } + function renameLocation(file: File, substring: string, options?: SpanFromSubstringOptions): RenameLocation { + return documentSpanFromSubstring(file, substring, options); + } interface SpanFromSubstringOptions { readonly index: number; } @@ -8284,12 +8295,12 @@ namespace ts.projectSystem { const response = executeSessionRequest(session, protocol.CommandTypes.Rename, { file: aFc, ...protocolLocationFromSubstring(cFile.content, "C") }); assert.equal(aFile.content, bFile.content); - const abLocs: protocol.TextSpan[] = [ - protocolTextSpanFromSubstring(aFile.content, "C"), - protocolTextSpanFromSubstring(aFile.content, "C", { index: 1 }), + const abLocs: protocol.RenameTextSpan[] = [ + protocolRenameSpanFromSubstring(aFile.content, "C"), + protocolRenameSpanFromSubstring(aFile.content, "C", { index: 1 }), ]; - const span = protocolTextSpanFromSubstring(cFile.content, "C"); - const cLocs: protocol.TextSpan[] = [span]; + const span = protocolRenameSpanFromSubstring(cFile.content, "C"); + const cLocs: protocol.RenameTextSpan[] = [span]; assert.deepEqual(response, { info: { canRename: true, @@ -8299,7 +8310,7 @@ namespace ts.projectSystem { kind: ScriptElementKind.constElement, kindModifiers: ScriptElementKindModifier.exportedModifier, localizedErrorMessage: undefined, - triggerSpan: span, + triggerSpan: protocolTextSpanFromSubstring(cFile.content, "C"), }, locs: [ { file: aFc, locs: cLocs }, @@ -9570,7 +9581,7 @@ export function Test2() { }); describe("tsserverProjectSystem rename", () => { - it("works", () => { + it("works with fileToRename", () => { const aTs: File = { path: "/a.ts", content: "export const a = 0;" }; const bTs: File = { path: "/b.ts", content: 'import { a } from "./a";' }; @@ -9589,7 +9600,36 @@ export function Test2() { localizedErrorMessage: undefined, triggerSpan: protocolTextSpanFromSubstring(bTs.content, "a", { index: 1 }), }, - locs: [{ file: bTs.path, locs: [protocolTextSpanFromSubstring(bTs.content, "./a")] }], + locs: [{ file: bTs.path, locs: [protocolRenameSpanFromSubstring(bTs.content, "./a")] }], + }); + }); + + it("works with prefixText and suffixText", () => { + const aTs: File = { path: "/a.ts", content: "const x = 0; const o = { x };" }; + const session = createSession(createServerHost([aTs])); + openFilesForSession([aTs], session); + + const response = executeSessionRequest(session, protocol.CommandTypes.Rename, protocolFileLocationFromSubstring(aTs, "x")); + assert.deepEqual(response, { + info: { + canRename: true, + fileToRename: undefined, + displayName: "x", + fullDisplayName: "x", + kind: ScriptElementKind.constElement, + kindModifiers: ScriptElementKindModifier.none, + localizedErrorMessage: undefined, + triggerSpan: protocolTextSpanFromSubstring(aTs.content, "x"), + }, + locs: [ + { + file: aTs.path, + locs: [ + protocolRenameSpanFromSubstring(aTs.content, "x"), + protocolRenameSpanFromSubstring(aTs.content, "x", { index: 1 }, { prefixText: "x: " }), + ], + }, + ], }); }); }); @@ -10070,13 +10110,13 @@ declare class TestLib { const renameATs = (aTs: File): protocol.SpanGroup => ({ file: aTs.path, - locs: [protocolTextSpanFromSubstring(aTs.content, "fnA")], + locs: [protocolRenameSpanFromSubstring(aTs.content, "fnA")], }); const renameUserTs = (userTs: File): protocol.SpanGroup => ({ file: userTs.path, locs: [ - protocolTextSpanFromSubstring(userTs.content, "fnA"), - protocolTextSpanFromSubstring(userTs.content, "fnA", { index: 1 }), + protocolRenameSpanFromSubstring(userTs.content, "fnA"), + protocolRenameSpanFromSubstring(userTs.content, "fnA", { index: 1 }), ], }); @@ -10123,9 +10163,9 @@ declare class TestLib { const session = makeSampleProjects(); const response = executeSessionRequest(session, protocol.CommandTypes.RenameLocationsFull, protocolFileLocationFromSubstring(userTs, "fnA()")); assert.deepEqual>(response, [ - documentSpanFromSubstring(userTs, "fnA"), - documentSpanFromSubstring(userTs, "fnA", { index: 1 }), - documentSpanFromSubstring(aTs, "fnA"), + renameLocation(userTs, "fnA"), + renameLocation(userTs, "fnA", { index: 1 }), + renameLocation(aTs, "fnA"), ]); verifyATsConfigOriginalProject(session); }); @@ -10148,13 +10188,13 @@ declare class TestLib { { file: userTs.path, locs: [ - protocolTextSpanFromSubstring(userTs.content, "fnB"), - protocolTextSpanFromSubstring(userTs.content, "fnB", { index: 1 }), + protocolRenameSpanFromSubstring(userTs.content, "fnB"), + protocolRenameSpanFromSubstring(userTs.content, "fnB", { index: 1 }), ], }, { file: bDts.path, - locs: [protocolTextSpanFromSubstring(bDts.content, "fnB")], + locs: [protocolRenameSpanFromSubstring(bDts.content, "fnB")], } ], }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7b3731ef281be..61fc5dbac4621 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4960,6 +4960,8 @@ declare namespace ts { originalFileName?: string; } interface RenameLocation extends DocumentSpan { + readonly prefixText?: string; + readonly suffixText?: string; } interface ReferenceEntry extends DocumentSpan { isWriteAccess: boolean; @@ -6475,7 +6477,11 @@ declare namespace ts.server.protocol { /** The file to which the spans apply */ file: string; /** The text spans in this group */ - locs: TextSpan[]; + locs: RenameTextSpan[]; + } + interface RenameTextSpan extends TextSpan { + readonly prefixText?: string; + readonly suffixText?: string; } interface RenameResponseBody { /** diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3a442e7f19cc0..54a37336d35cd 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4960,6 +4960,8 @@ declare namespace ts { originalFileName?: string; } interface RenameLocation extends DocumentSpan { + readonly prefixText?: string; + readonly suffixText?: string; } interface ReferenceEntry extends DocumentSpan { isWriteAccess: boolean; diff --git a/tests/cases/fourslash/findAllReferencesDynamicImport3.ts b/tests/cases/fourslash/findAllReferencesDynamicImport3.ts index 636e640ceb355..371369ac39ce7 100644 --- a/tests/cases/fourslash/findAllReferencesDynamicImport3.ts +++ b/tests/cases/fourslash/findAllReferencesDynamicImport3.ts @@ -10,4 +10,5 @@ verify.referenceGroups(r1, [ { definition: "function bar(): string", ranges: [r0] }, { definition: "var bar: () => string", ranges: [r1] }, ]); -verify.rangesAreRenameLocations(); +verify.renameLocations(r0, [r0, { range: r1, suffixText: ": bar" }]); +verify.renameLocations(r1, [{ range: r1, prefixText: "bar: " }]) diff --git a/tests/cases/fourslash/findAllRefsDestructureGetter.ts b/tests/cases/fourslash/findAllRefsDestructureGetter.ts index b21e6186eb272..763d3cfb0e5ab 100644 --- a/tests/cases/fourslash/findAllRefsDestructureGetter.ts +++ b/tests/cases/fourslash/findAllRefsDestructureGetter.ts @@ -9,14 +9,16 @@ ////[|x|]; [|y|]; const [x0, y0, x1, y1, x2, y2] = test.ranges(); -verify.referenceGroups(x0, [{ definition: "(property) Test.x: number", ranges: [x0, x1, x2] }]); -verify.referenceGroups([x1, x2], [ +verify.referenceGroups(x0, [{ definition: "(property) Test.x: number", ranges: [x0, x1] }]); +verify.referenceGroups(x1, [ { definition: "(property) Test.x: number", ranges: [x0] }, { definition: "const x: number", ranges: [x1, x2] }, ]); +verify.referenceGroups(x2, [{ definition: "const x: number", ranges: [x1, x2] }]); -verify.referenceGroups(y0, [{ definition: "(property) Test.y: number", ranges: [y0, y1, y2] }]); -verify.referenceGroups([y1, y2], [ +verify.referenceGroups(y0, [{ definition: "(property) Test.y: number", ranges: [y0, y1] }]); +verify.referenceGroups(y1, [ { definition: "(property) Test.y: number", ranges: [y0] }, { definition: "const y: number", ranges: [y1, y2] }, ]); +verify.referenceGroups(y2, [{ definition: "const y: number", ranges: [y1, y2] }]); diff --git a/tests/cases/fourslash/findAllRefsObjectBindingElementPropertyName04.ts b/tests/cases/fourslash/findAllRefsObjectBindingElementPropertyName04.ts index fe0d659f0ec4e..c3bebdb262314 100644 --- a/tests/cases/fourslash/findAllRefsObjectBindingElementPropertyName04.ts +++ b/tests/cases/fourslash/findAllRefsObjectBindingElementPropertyName04.ts @@ -14,9 +14,9 @@ const ranges = test.ranges(); const [r0, r1, r2, r3] = ranges; -verify.referenceGroups([r0, r1], [{ definition: "(property) I.property1: number", ranges }]); -verify.referenceGroups([r2, r3], [ +verify.referenceGroups([r0, r1], [{ definition: "(property) I.property1: number", ranges: [r0, r1, r2] }]); +verify.referenceGroups(r2, [ { definition: "(property) I.property1: number", ranges: [r0, r1] }, - { definition: "var property1: number", ranges: [r2, r3] } + { definition: "var property1: number", ranges: [r2, r3] }, ]); - +verify.referenceGroups(r3, [{ definition: "var property1: number", ranges: [r2, r3] }]); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index e254b2b51db47..4603d61a286f9 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -317,7 +317,7 @@ declare namespace FourSlashInterface { }[]): void; renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, range?: Range): void; renameInfoFailed(message?: string): void; - renameLocations(startRanges: ArrayOrSingle, options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }): void; + renameLocations(startRanges: ArrayOrSingle, options: RenameLocationsOptions): void; /** Verify the quick info available at the current marker. */ quickInfoIs(expectedText: string, expectedDocumentation?: string): void; @@ -648,6 +648,13 @@ declare namespace FourSlashInterface { type ArrayOrSingle = T | ReadonlyArray; type NewFileContent = string | { readonly [fileName: string]: string }; + + type RenameLocationsOptions = ReadonlyArray | { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + readonly ranges: ReadonlyArray; + } + type RenameLocationOptions = Range | { readonly range: Range, readonly prefixText?: string, readonly suffixText?: string }; } declare function verifyOperationIsCancelled(f: any): void; declare var test: FourSlashInterface.test_; diff --git a/tests/cases/fourslash/getOccurrencesIsDefinitionOfBindingPattern.ts b/tests/cases/fourslash/getOccurrencesIsDefinitionOfBindingPattern.ts index 58814b45e99d0..32fec74d162b1 100644 --- a/tests/cases/fourslash/getOccurrencesIsDefinitionOfBindingPattern.ts +++ b/tests/cases/fourslash/getOccurrencesIsDefinitionOfBindingPattern.ts @@ -3,8 +3,8 @@ ////const z = [|x|]; const [r0, r1, r2] = test.ranges(); -verify.referenceGroups([r0, r2], [ - { definition: "const x: number", ranges: [r0, r2] }, - { definition: "(property) x: number", ranges: [r1] }, -]); -verify.referenceGroups(r1, [{ definition: "(property) x: number", ranges: [r0, r1, r2] }]); +const local = { definition: "const x: number", ranges: [r0, r2] }; +const prop = { definition: "(property) x: number", ranges: [r1] }; +verify.referenceGroups(r0, [local, prop]); +verify.referenceGroups(r1, [{ ...prop, ranges: [r0, r1] }]); +verify.referenceGroups(r2, [local]); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts b/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts index 0ef6f672e0002..2c8ad51f48697 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts @@ -6,13 +6,15 @@ ////} ////var elems: I[]; //// -////var p2: number, property1: number; +////var p2: number, [|property1|]: number; ////for ({ [|property1|] } = elems[0]; p2 < 100; p2++) { -//// p2 = property1++; +//// p2 = [|property1|]++; ////} ////for ({ [|property1|]: p2 } = elems[0]; p2 < 100; p2++) { ////} +verify.noErrors(); const ranges = test.ranges(); -const [r0, , r2] = ranges; -verify.renameLocations([r0, r2], ranges); +const [r0, r1, r2, r3, r4] = ranges; +verify.renameLocations([r0, r4], [r0, { range: r2, suffixText: ": property1" }, r4]); +verify.renameLocations([r1, r2, r3], [r1, { range: r2, prefixText: "property1: " }, r3]); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInFor2.ts b/tests/cases/fourslash/renameDestructuringAssignmentInFor2.ts deleted file mode 100644 index 17173d0da4dd6..0000000000000 --- a/tests/cases/fourslash/renameDestructuringAssignmentInFor2.ts +++ /dev/null @@ -1,20 +0,0 @@ -/// - -////interface I { -//// [|property1|]: number; -//// property2: string; -////} -////var elems: I[]; -//// -////var p2: number, [|property1|]: number; -////for ({ [|property1|] } = elems[0]; p2 < 100; p2++) { -//// p2 = [|property1|]++; -////} -////for ({ [|property1|]: p2 } = elems[0]; p2 < 100; p2++) { -////} - -const ranges = test.ranges(); -const [r0, r1, r2, r3, r4] = ranges; -verify.renameLocations([r0, r4], [r0, r2, r4]); -verify.renameLocations([r1, r3], [r1, r2, r3]); -verify.renameLocations(r2, ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts b/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts index b452ca7c9a70c..38703a68b99b0 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts @@ -6,13 +6,15 @@ ////} ////var elems: I[]; //// -////var property1: number, p2: number; +////var [|property1|]: number, p2: number; ////for ({ [|property1|] } of elems) { -//// property1++; +//// [|property1|]++; ////} ////for ({ [|property1|]: p2 } of elems) { ////} +verify.noErrors(); const ranges = test.ranges(); -const [r0, , r2] = ranges; -verify.renameLocations([r0, r2], ranges); +const [r0, r1, r2, r3, r4] = ranges; +verify.renameLocations([r0, r4], [r0, { range: r2, suffixText: ": property1" }, r4]); +verify.renameLocations([r1, r2, r3], [r1, { range: r2, prefixText: "property1: " }, r3]); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInForOf2.ts b/tests/cases/fourslash/renameDestructuringAssignmentInForOf2.ts deleted file mode 100644 index b7432e08a264b..0000000000000 --- a/tests/cases/fourslash/renameDestructuringAssignmentInForOf2.ts +++ /dev/null @@ -1,20 +0,0 @@ -/// - -////interface I { -//// [|property1|]: number; -//// property2: string; -////} -////var elems: I[]; -//// -////var [|property1|]: number, p2: number; -////for ({ [|property1|] } of elems) { -//// [|property1|]++; -////} -////for ({ [|property1|]: p2 } of elems) { -////} - -const ranges = test.ranges(); -const [r0, r1, r2, r3, r4] = ranges; -verify.renameLocations([r0, r4], [r0, r2, r4]); -verify.renameLocations([r1, r3], [r1, r2, r3]); -verify.renameLocations(r2, ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts index 57191e6d6ab70..292aaf1bf6d69 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts @@ -4,10 +4,12 @@ //// [|property1|]: number; //// property2: string; ////} -////var elems: I[], p1: number, property1: number; +////var elems: I[], p1: number, [|property1|]: number; ////[{ [|property1|]: p1 }] = elems; ////[{ [|property1|] }] = elems; const ranges = test.ranges(); -const [r0, r1] = ranges; -verify.renameLocations([r0, r1], ranges); +const [r0, r1, r2, r3] = ranges; +verify.renameLocations([r0, r2], [r0, r2, { range: r3, suffixText: ": property1" }]); +verify.renameLocations([r1, r3], [r1, { range: r3, prefixText: "property1: " }]); + diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral2.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral2.ts deleted file mode 100644 index 1f18c443ca6b4..0000000000000 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral2.ts +++ /dev/null @@ -1,15 +0,0 @@ -/// - -////interface I { -//// [|property1|]: number; -//// property2: string; -////} -////var elems: I[], p1: number, [|property1|]: number; -////[{ [|property1|]: p1 }] = elems; -////[{ [|property1|] }] = elems; - -const ranges = test.ranges(); -const [r0, r1, r2, r3] = ranges; -verify.renameLocations([r0, r2], [r0, r2, r3]); -verify.renameLocations(r1, [r1, r3]); -verify.renameLocations(r3, ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts index fe8961aa18c05..e83487d15d250 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts @@ -7,14 +7,16 @@ //// secondary: string; //// }; ////} -////let multiRobot: MultiRobot; +////let multiRobot: MultiRobot, [|primary|]: string, secondary: string, primaryA: string, secondaryA: string, i: number; ////for ({ skills: { [|primary|]: primaryA, secondary: secondaryA } } = multiRobot, i = 0; i < 1; i++) { -//// console.log(primaryA); +//// primaryA; ////} ////for ({ skills: { [|primary|], secondary } } = multiRobot, i = 0; i < 1; i++) { -//// console.log(primary); +//// [|primary|]; ////} +verify.noErrors(); const ranges = test.ranges(); -const [r0, r1] = ranges; -verify.renameLocations([r0, r1], ranges); +const [r0, r1, r2, r3, r4] = ranges; +verify.renameLocations([r0, r2], [r0, r2, { range: r3, suffixText: ": primary" }]); +verify.renameLocations([r1, r3, r4], [r1, { range: r3, prefixText: "primary: " }, r4]); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor2.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor2.ts index c5e7212c99bb5..e83487d15d250 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor2.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor2.ts @@ -7,16 +7,16 @@ //// secondary: string; //// }; ////} -////let multiRobot: MultiRobot, [|primary|]: string; +////let multiRobot: MultiRobot, [|primary|]: string, secondary: string, primaryA: string, secondaryA: string, i: number; ////for ({ skills: { [|primary|]: primaryA, secondary: secondaryA } } = multiRobot, i = 0; i < 1; i++) { -//// console.log(primaryA); +//// primaryA; ////} ////for ({ skills: { [|primary|], secondary } } = multiRobot, i = 0; i < 1; i++) { -//// console.log([|primary|]); +//// [|primary|]; ////} +verify.noErrors(); const ranges = test.ranges(); const [r0, r1, r2, r3, r4] = ranges; -verify.renameLocations([r0, r2], [r0, r2, r3]); -verify.renameLocations([r1, r4], [r1, r3, r4]); -verify.renameLocations(r3, ranges); +verify.renameLocations([r0, r2], [r0, r2, { range: r3, suffixText: ": primary" }]); +verify.renameLocations([r1, r3, r4], [r1, { range: r3, prefixText: "primary: " }, r4]); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts index 7d05760074803..6a9eb6235b7ba 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts @@ -8,13 +8,15 @@ //// }; ////} ////let multiRobots: MultiRobot[]; +////let [|primary|]: string, secondary: string, primaryA: string, secondaryA: string; ////for ({ skills: { [|primary|]: primaryA, secondary: secondaryA } } of multiRobots) { -//// console.log(primaryA); +//// primaryA; ////} ////for ({ skills: { [|primary|], secondary } } of multiRobots) { -//// console.log(primary); +//// [|primary|]; ////} -const ranges = test.ranges(); -const [r0, r1] = ranges; -verify.renameLocations([r0, r1], ranges); +verify.noErrors(); +const [r0, r1, r2, r3, r4] = test.ranges(); +verify.renameLocations([r0, r2], [r0, r2, { range: r3, suffixText: ": primary" }]); +verify.renameLocations([r1, r3, r4], [r1, { range: r3, prefixText: "primary: " }, r4]) diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf2.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf2.ts index 74dfda39bc211..7c5c288ea78fe 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf2.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf2.ts @@ -17,6 +17,5 @@ const ranges = test.ranges(); const [r0, r1, r2, r3, r4] = ranges; -verify.renameLocations([r0, r2], [r0, r2, r3]); -verify.renameLocations([r1, r4], [r1, r3, r4]); -verify.renameLocations(r3, ranges); +verify.renameLocations([r0, r2], [r0, r2, { range: r3, suffixText: ": primary" }]); +verify.renameLocations([r1, r3, r4], [r1, { range: r3, prefixText: "primary: " }, r4]); diff --git a/tests/cases/fourslash/renameDestructuringClassProperty.ts b/tests/cases/fourslash/renameDestructuringClassProperty.ts index 11261a4df78d9..ac708e554717e 100644 --- a/tests/cases/fourslash/renameDestructuringClassProperty.ts +++ b/tests/cases/fourslash/renameDestructuringClassProperty.ts @@ -16,4 +16,7 @@ //// } ////} -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3, r4] = test.ranges(); +verify.renameLocations([r0, r2], [r0, { range: r1, suffixText: ": foo" }, r2, { range: r3, suffixText: ": foo" }]); +verify.renameLocations(r1, [{ range: r1, prefixText: "foo: " }]); +verify.renameLocations([r3, r4], [{ range: r3, prefixText: "foo: " }, r4]); diff --git a/tests/cases/fourslash/renameDestructuringDeclarationInFor.ts b/tests/cases/fourslash/renameDestructuringDeclarationInFor.ts index 0338433ae727d..02bc0f508269c 100644 --- a/tests/cases/fourslash/renameDestructuringDeclarationInFor.ts +++ b/tests/cases/fourslash/renameDestructuringDeclarationInFor.ts @@ -13,4 +13,6 @@ //// [|property1|] = p2; ////} -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3] = test.ranges(); +verify.renameLocations([r0, r1], [r0, r1, { range: r2, suffixText: ": property1" }]); +verify.renameLocations([r2, r3], [{ range: r2, prefixText: "property1: " }, r3]); diff --git a/tests/cases/fourslash/renameDestructuringDeclarationInForOf.ts b/tests/cases/fourslash/renameDestructuringDeclarationInForOf.ts index fcd55a5bc2001..a8e21c83cf35a 100644 --- a/tests/cases/fourslash/renameDestructuringDeclarationInForOf.ts +++ b/tests/cases/fourslash/renameDestructuringDeclarationInForOf.ts @@ -12,4 +12,6 @@ ////for (let { [|property1|]: p2 } of elems) { ////} -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3] = test.ranges(); +verify.renameLocations([r0, r3], [r0, { range: r1, suffixText: ": property1" }, r3]); +verify.renameLocations([r1, r2], [{ range: r1, prefixText: "property1: " }, r2]); diff --git a/tests/cases/fourslash/renameDestructuringFunctionParameter.ts b/tests/cases/fourslash/renameDestructuringFunctionParameter.ts index 4abbabc37234e..d6a732176d204 100644 --- a/tests/cases/fourslash/renameDestructuringFunctionParameter.ts +++ b/tests/cases/fourslash/renameDestructuringFunctionParameter.ts @@ -4,4 +4,8 @@ //// f({[|a|]}); ////} -verify.rangesAreRenameLocations(); +const [r0, r1, r2] = test.ranges(); +// renames the local +verify.renameLocations([r0, r2], [{ range: r0, prefixText: "a: " }, { range: r2, prefixText: "a: " }]); +// renames the property +verify.renameLocations(r1, [{ range: r0, suffixText: ": a" }, r1, { range: r2, suffixText: ": a" }]); diff --git a/tests/cases/fourslash/renameDestructuringNestedBindingElement.ts b/tests/cases/fourslash/renameDestructuringNestedBindingElement.ts index 7557744ecee2a..6643382f6c91e 100644 --- a/tests/cases/fourslash/renameDestructuringNestedBindingElement.ts +++ b/tests/cases/fourslash/renameDestructuringNestedBindingElement.ts @@ -15,4 +15,6 @@ //// console.log([|primary|]); ////} -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3] = test.ranges(); +verify.renameLocations([r0, r1], [r0, r1, { range: r2, suffixText: ": primary" }]); +verify.renameLocations([r2, r3], [{ range: r2, prefixText: "primary: " }, r3]); diff --git a/tests/cases/fourslash/renameImportAndShorthand.ts b/tests/cases/fourslash/renameImportAndShorthand.ts index 4836d8c14fefd..01be6650b3b1e 100644 --- a/tests/cases/fourslash/renameImportAndShorthand.ts +++ b/tests/cases/fourslash/renameImportAndShorthand.ts @@ -3,4 +3,5 @@ ////import [|foo|] from 'bar'; ////const bar = { [|foo|] }; -verify.rangesAreRenameLocations(); +const [r0, r1] = test.ranges(); +verify.renameLocations([r0, r1], [r0, { range: r1, prefixText: "foo: " }]); diff --git a/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts b/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts index 21cdc0fa82026..e577d82d3ae60 100644 --- a/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts +++ b/tests/cases/fourslash/renameImportNamespaceAndShorthand.ts @@ -3,4 +3,5 @@ ////import * as [|foo|] from 'bar'; ////const bar = { [|foo|] }; -verify.rangesAreRenameLocations(); +const [r0, r1] = test.ranges(); +verify.renameLocations([r0, r1], [r0, { range: r1, prefixText: "foo: " }]); diff --git a/tests/cases/fourslash/renameImportRequire.ts b/tests/cases/fourslash/renameImportRequire.ts index 8a9aeebc32891..bf29a0999b136 100644 --- a/tests/cases/fourslash/renameImportRequire.ts +++ b/tests/cases/fourslash/renameImportRequire.ts @@ -5,4 +5,6 @@ ////a = { [|e|] }; ////export { [|e|] }; -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3] = test.ranges(); +//TODO: export should have prefix/suffix too +verify.renameLocations([r0, r1, r2, r3], [r0, r1, { range: r2, prefixText: "e: " }, r3]); diff --git a/tests/cases/fourslash/renameParameterPropertyDeclaration4.ts b/tests/cases/fourslash/renameParameterPropertyDeclaration4.ts index ca201d28b2c0d..292c82a2d523f 100644 --- a/tests/cases/fourslash/renameParameterPropertyDeclaration4.ts +++ b/tests/cases/fourslash/renameParameterPropertyDeclaration4.ts @@ -6,4 +6,5 @@ //// } //// } -verify.rangesAreRenameLocations(); +const [r0, r1] = test.ranges(); +verify.renameLocations([r0, r1], [{ range: r0, prefixText: "protectedParam: " }, r1]);