Skip to content

Commit 591a0db

Browse files
Merge pull request #3367 from Microsoft/fixDeFaultOfFindAllRefsToMaster
Fix findAllRefs, getHighlightSpans, renameLocs, renameInfo for default exports and functions expressions
2 parents d9ca99d + cdc8c3b commit 591a0db

25 files changed

+507
-67
lines changed

Diff for: src/harness/fourslash.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ module FourSlash {
743743
var reference = references[i];
744744
if (reference && reference.fileName === fileName && reference.textSpan.start === start && ts.textSpanEnd(reference.textSpan) === end) {
745745
if (typeof isWriteAccess !== "undefined" && reference.isWriteAccess !== isWriteAccess) {
746-
this.raiseError('verifyReferencesAtPositionListContains failed - item isWriteAccess value doe not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.');
746+
this.raiseError('verifyReferencesAtPositionListContains failed - item isWriteAccess value does not match, actual: ' + reference.isWriteAccess + ', expected: ' + isWriteAccess + '.');
747747
}
748748
return;
749749
}
@@ -884,8 +884,16 @@ module FourSlash {
884884
this.activeFile.fileName, this.currentCaretPosition, findInStrings, findInComments);
885885

886886
var ranges = this.getRanges();
887+
888+
if (!references) {
889+
if (ranges.length !== 0) {
890+
this.raiseError(`Expected ${ranges.length} rename locations; got none.`);
891+
}
892+
return;
893+
}
894+
887895
if (ranges.length !== references.length) {
888-
this.raiseError(this.assertionMessage("Rename locations", references.length, ranges.length));
896+
this.raiseError("Rename location count does not match result.\n\nExpected: " + JSON.stringify(ranges) + "\n\nActual:" + JSON.stringify(references));
889897
}
890898

891899
ranges = ranges.sort((r1, r2) => r1.start - r2.start);
@@ -898,9 +906,7 @@ module FourSlash {
898906
if (reference.textSpan.start !== range.start ||
899907
ts.textSpanEnd(reference.textSpan) !== range.end) {
900908

901-
this.raiseError(this.assertionMessage("Rename location",
902-
"[" + reference.textSpan.start + "," + ts.textSpanEnd(reference.textSpan) + ")",
903-
"[" + range.start + "," + range.end + ")"));
909+
this.raiseError("Rename location results do not match.\n\nExpected: " + JSON.stringify(ranges) + "\n\nActual:" + JSON.stringify(references));
904910
}
905911
}
906912
}

Diff for: src/services/services.ts

+31-62
Original file line numberDiff line numberDiff line change
@@ -4265,7 +4265,7 @@ namespace ts {
42654265
isLiteralNameOfPropertyDeclarationOrIndexAccess(node) ||
42664266
isNameOfExternalModuleImportOrDeclaration(node)) {
42674267

4268-
let referencedSymbols = getReferencedSymbolsForNodes(node, sourceFilesToSearch, /*findInStrings:*/ false, /*findInComments:*/ false);
4268+
let referencedSymbols = getReferencedSymbolsForNode(node, sourceFilesToSearch, /*findInStrings:*/ false, /*findInComments:*/ false);
42694269
return convertReferencedSymbols(referencedSymbols);
42704270
}
42714271

@@ -4917,10 +4917,10 @@ namespace ts {
49174917
}
49184918

49194919
Debug.assert(node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral);
4920-
return getReferencedSymbolsForNodes(node, program.getSourceFiles(), findInStrings, findInComments);
4920+
return getReferencedSymbolsForNode(node, program.getSourceFiles(), findInStrings, findInComments);
49214921
}
49224922

4923-
function getReferencedSymbolsForNodes(node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] {
4923+
function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], findInStrings: boolean, findInComments: boolean): ReferencedSymbol[] {
49244924
let typeChecker = program.getTypeChecker();
49254925

49264926
// Labels
@@ -4955,7 +4955,7 @@ namespace ts {
49554955

49564956
let declarations = symbol.declarations;
49574957

4958-
// The symbol was an internal symbol and does not have a declaration e.g.undefined symbol
4958+
// The symbol was an internal symbol and does not have a declaration e.g. undefined symbol
49594959
if (!declarations || !declarations.length) {
49604960
return undefined;
49614961
}
@@ -4965,8 +4965,9 @@ namespace ts {
49654965
// Compute the meaning from the location and the symbol it references
49664966
let searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations);
49674967

4968-
// Get the text to search for, we need to normalize it as external module names will have quote
4969-
let declaredName = getDeclaredName(symbol, node);
4968+
// Get the text to search for.
4969+
// Note: if this is an external module symbol, the name doesn't include quotes.
4970+
let declaredName = getDeclaredName(typeChecker, symbol, node);
49704971

49714972
// Try to get the smallest valid scope that we can limit our search to;
49724973
// otherwise we'll need to search globally (i.e. include each file).
@@ -5013,76 +5014,43 @@ namespace ts {
50135014
};
50145015
}
50155016

5016-
function isImportOrExportSpecifierName(location: Node): boolean {
5017-
return location.parent &&
5018-
(location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) &&
5019-
(<ImportOrExportSpecifier>location.parent).propertyName === location;
5020-
}
5021-
50225017
function isImportOrExportSpecifierImportSymbol(symbol: Symbol) {
50235018
return (symbol.flags & SymbolFlags.Alias) && forEach(symbol.declarations, declaration => {
50245019
return declaration.kind === SyntaxKind.ImportSpecifier || declaration.kind === SyntaxKind.ExportSpecifier;
50255020
});
50265021
}
50275022

5028-
function getDeclaredName(symbol: Symbol, location: Node) {
5029-
// Special case for function expressions, whose names are solely local to their bodies.
5030-
let functionExpression = forEach(symbol.declarations, d => d.kind === SyntaxKind.FunctionExpression ? <FunctionExpression>d : undefined);
5031-
5032-
// When a name gets interned into a SourceFile's 'identifiers' Map,
5033-
// its name is escaped and stored in the same way its symbol name/identifier
5034-
// name should be stored. Function expressions, however, are a special case,
5035-
// because despite sometimes having a name, the binder unconditionally binds them
5036-
// to a symbol with the name "__function".
5037-
let name: string;
5038-
if (functionExpression && functionExpression.name) {
5039-
name = functionExpression.name.text;
5040-
}
5041-
5042-
// If this is an export or import specifier it could have been renamed using the as syntax.
5043-
// if so we want to search for whatever under the cursor, the symbol is pointing to the alias (name)
5044-
// so check for the propertyName.
5045-
if (isImportOrExportSpecifierName(location)) {
5046-
return location.getText();
5047-
}
5048-
5049-
name = typeChecker.symbolToString(symbol);
5050-
5051-
return stripQuotes(name);
5052-
}
5053-
50545023
function getInternedName(symbol: Symbol, location: Node, declarations: Declaration[]): string {
5055-
// If this is an export or import specifier it could have been renamed using the as syntax.
5056-
// if so we want to search for whatever under the cursor, the symbol is pointing to the alias (name)
5057-
// so check for the propertyName.
5024+
// If this is an export or import specifier it could have been renamed using the 'as' syntax.
5025+
// If so we want to search for whatever under the cursor.
50585026
if (isImportOrExportSpecifierName(location)) {
50595027
return location.getText();
50605028
}
50615029

5062-
// Special case for function expressions, whose names are solely local to their bodies.
5063-
let functionExpression = forEach(declarations, d => d.kind === SyntaxKind.FunctionExpression ? <FunctionExpression>d : undefined);
5064-
5065-
// When a name gets interned into a SourceFile's 'identifiers' Map,
5066-
// its name is escaped and stored in the same way its symbol name/identifier
5067-
// name should be stored. Function expressions, however, are a special case,
5068-
// because despite sometimes having a name, the binder unconditionally binds them
5069-
// to a symbol with the name "__function".
5070-
let name = functionExpression && functionExpression.name
5071-
? functionExpression.name.text
5072-
: symbol.name;
5073-
5074-
return stripQuotes(name);
5075-
}
5030+
// Try to get the local symbol if we're dealing with an 'export default'
5031+
// since that symbol has the "true" name.
5032+
let localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol);
5033+
symbol = localExportDefaultSymbol || symbol;
50765034

5077-
function stripQuotes(name: string) {
5078-
let length = name.length;
5079-
if (length >= 2 && name.charCodeAt(0) === CharacterCodes.doubleQuote && name.charCodeAt(length - 1) === CharacterCodes.doubleQuote) {
5080-
return name.substring(1, length - 1);
5081-
};
5082-
return name;
5035+
return stripQuotes(symbol.name);
50835036
}
50845037

5038+
/**
5039+
* Determines the smallest scope in which a symbol may have named references.
5040+
* Note that not every construct has been accounted for. This function can
5041+
* probably be improved.
5042+
*
5043+
* @returns undefined if the scope cannot be determined, implying that
5044+
* a reference to a symbol can occur anywhere.
5045+
*/
50855046
function getSymbolScope(symbol: Symbol): Node {
5047+
// If this is the symbol of a function expression, then named references
5048+
// are limited to its own scope.
5049+
let valueDeclaration = symbol.valueDeclaration;
5050+
if (valueDeclaration && valueDeclaration.kind === SyntaxKind.FunctionExpression) {
5051+
return valueDeclaration;
5052+
}
5053+
50865054
// If this is private property or method, the scope is the containing class
50875055
if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) {
50885056
let privateDeclaration = forEach(symbol.getDeclarations(), d => (d.flags & NodeFlags.Private) ? d : undefined);
@@ -6724,12 +6692,13 @@ namespace ts {
67246692
}
67256693
}
67266694

6695+
let displayName = getDeclaredName(typeChecker, symbol, node);
67276696
let kind = getSymbolKind(symbol, node);
67286697
if (kind) {
67296698
return {
67306699
canRename: true,
67316700
localizedErrorMessage: undefined,
6732-
displayName: symbol.name,
6701+
displayName,
67336702
fullDisplayName: typeChecker.getFullyQualifiedName(symbol),
67346703
kind: kind,
67356704
kindModifiers: getSymbolModifiers(symbol),

Diff for: src/services/utilities.ts

+30
Original file line numberDiff line numberDiff line change
@@ -652,4 +652,34 @@ namespace ts {
652652
typechecker.getSymbolDisplayBuilder().buildSignatureDisplay(signature, writer, enclosingDeclaration, flags);
653653
});
654654
}
655+
656+
export function getDeclaredName(typeChecker: TypeChecker, symbol: Symbol, location: Node): string {
657+
// If this is an export or import specifier it could have been renamed using the 'as' syntax.
658+
// If so we want to search for whatever is under the cursor.
659+
if (isImportOrExportSpecifierName(location)) {
660+
return location.getText();
661+
}
662+
663+
// Try to get the local symbol if we're dealing with an 'export default'
664+
// since that symbol has the "true" name.
665+
let localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol);
666+
667+
let name = typeChecker.symbolToString(localExportDefaultSymbol || symbol);
668+
669+
return stripQuotes(name);
670+
}
671+
672+
export function isImportOrExportSpecifierName(location: Node): boolean {
673+
return location.parent &&
674+
(location.parent.kind === SyntaxKind.ImportSpecifier || location.parent.kind === SyntaxKind.ExportSpecifier) &&
675+
(<ImportOrExportSpecifier>location.parent).propertyName === location;
676+
}
677+
678+
export function stripQuotes(name: string) {
679+
let length = name.length;
680+
if (length >= 2 && name.charCodeAt(0) === CharacterCodes.doubleQuote && name.charCodeAt(length - 1) === CharacterCodes.doubleQuote) {
681+
return name.substring(1, length - 1);
682+
};
683+
return name;
684+
}
655685
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////export default class [|DefaultExportedClass|] {
4+
////}
5+
////
6+
////var x: [|DefaultExportedClass|];
7+
////
8+
////var y = new [|DefaultExportedClass|];
9+
10+
let ranges = test.ranges()
11+
for (let range of ranges) {
12+
goTo.position(range.start);
13+
14+
verify.referencesCountIs(ranges.length);
15+
for (let expectedReference of ranges) {
16+
verify.referencesAtPositionContains(expectedReference);
17+
}
18+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////export default function [|DefaultExportedFunction|]() {
4+
//// return [|DefaultExportedFunction|]
5+
////}
6+
////
7+
////var x: typeof [|DefaultExportedFunction|];
8+
////
9+
////var y = [|DefaultExportedFunction|]();
10+
11+
let ranges = test.ranges()
12+
for (let range of ranges) {
13+
goTo.position(range.start);
14+
15+
verify.referencesCountIs(ranges.length);
16+
for (let expectedReference of ranges) {
17+
verify.referencesAtPositionContains(expectedReference);
18+
}
19+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////function [|f|]() {
4+
//// return 100;
5+
////}
6+
////
7+
////export default [|f|];
8+
////
9+
////var x: typeof [|f|];
10+
////
11+
////var y = [|f|]();
12+
////
13+
////namespace [|f|] {
14+
//// var local = 100;
15+
////}
16+
17+
let ranges = test.ranges()
18+
for (let range of ranges) {
19+
goTo.position(range.start);
20+
21+
verify.referencesCountIs(ranges.length);
22+
for (let expectedReference of ranges) {
23+
verify.referencesAtPositionContains(expectedReference);
24+
}
25+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////function f() {
4+
//// return 100;
5+
////}
6+
////
7+
////export default [|f|];
8+
////
9+
////var x: typeof f;
10+
////
11+
////var y = f();
12+
////
13+
////namespace /**/[|f|] {
14+
////}
15+
16+
// The function 'f' and the namespace 'f' don't get merged,
17+
// but the 'export default' site, includes both meanings.
18+
19+
// Here we are testing whether the 'export default'
20+
// site is included in the references to the namespace.
21+
22+
goTo.marker();
23+
let ranges = test.ranges();
24+
verify.referencesCountIs(ranges.length);
25+
26+
for (let expectedReference of ranges) {
27+
verify.referencesAtPositionContains(expectedReference);
28+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////function /**/[|f|]() {
4+
//// return 100;
5+
////}
6+
////
7+
////export default [|f|];
8+
////
9+
////var x: typeof [|f|];
10+
////
11+
////var y = [|f|]();
12+
////
13+
////namespace f {
14+
////}
15+
16+
// The function 'f' and the namespace 'f' don't get merged,
17+
// but the 'export default' site, includes both meanings.
18+
19+
// Here we are testing whether the 'export default' site
20+
// and all value-uses of 'f' are included in the references to the function.
21+
22+
goTo.marker();
23+
let ranges = test.ranges();
24+
verify.referencesCountIs(ranges.length);
25+
26+
for (let expectedReference of ranges) {
27+
verify.referencesAtPositionContains(expectedReference);
28+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////function [|f|]() {
4+
//// return 100;
5+
////}
6+
////
7+
////export default /**/[|f|];
8+
////
9+
////var x: typeof [|f|];
10+
////
11+
////var y = [|f|]();
12+
////
13+
////namespace [|f|] {
14+
////}
15+
16+
// The function 'f' and the namespace 'f' don't get merged,
17+
// but the 'export default' site, includes both meanings.
18+
19+
// Here we are testing whether the 'export default' site
20+
// and all value-uses of 'f' are included in the references to the function.
21+
22+
goTo.marker();
23+
let ranges = test.ranges();
24+
verify.referencesCountIs(ranges.length);
25+
26+
for (let expectedReference of ranges) {
27+
verify.referencesAtPositionContains(expectedReference);
28+
}

0 commit comments

Comments
 (0)