Skip to content

Commit 5141f1d

Browse files
Merge pull request #2111 from Microsoft/navToPatternMatcher
Move NavigateTo over to using the new pattern matcher.
2 parents 09e3cd6 + f90f8e8 commit 5141f1d

18 files changed

+260
-153
lines changed

src/server/client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ module ts.server {
229229
kind: entry.kind,
230230
kindModifiers: entry.kindModifiers,
231231
matchKind: entry.matchKind,
232+
isCaseSensitive: entry.isCaseSensitive,
232233
fileName: fileName,
233234
textSpan: ts.createTextSpanFromBounds(start, end)
234235
};

src/server/protocol.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,11 @@ declare module ts.server.protocol {
711711
* exact, substring, or prefix.
712712
*/
713713
matchKind?: string;
714+
715+
/**
716+
* If this was a case sensitive or insensitive match.
717+
*/
718+
isCaseSensitive?: boolean;
714719

715720
/**
716721
* Optional modifiers for the kind (such as 'public').

src/services/navigateTo.ts

Lines changed: 157 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,46 @@
11
module ts.NavigateTo {
2-
type RawNavigateToItem = { name: string; fileName: string; matchKind: MatchKind; declaration: Declaration };
3-
4-
enum MatchKind {
5-
none = 0,
6-
exact = 1,
7-
substring = 2,
8-
prefix = 3
9-
}
10-
11-
export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[]{
12-
// Split search value in terms array
13-
var terms = searchValue.split(" ");
14-
15-
// default NavigateTo approach: if search term contains only lower-case chars - use case-insensitive search, otherwise switch to case-sensitive version
16-
var searchTerms = map(terms, t => ({ caseSensitive: hasAnyUpperCaseCharacter(t), term: t }));
2+
type RawNavigateToItem = { name: string; fileName: string; matchKind: PatternMatchKind; isCaseSensitive: boolean; declaration: Declaration };
173

4+
export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[] {
5+
var patternMatcher = createPatternMatcher(searchValue);
186
var rawItems: RawNavigateToItem[] = [];
197

208
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
219
forEach(program.getSourceFiles(), sourceFile => {
2210
cancellationToken.throwIfCancellationRequested();
2311

24-
var fileName = sourceFile.fileName;
2512
var declarations = sourceFile.getNamedDeclarations();
2613
for (var i = 0, n = declarations.length; i < n; i++) {
2714
var declaration = declarations[i];
28-
// TODO(jfreeman): Skip this declaration if it has a computed name
29-
var name = (<Identifier>declaration.name).text;
30-
var matchKind = getMatchKind(searchTerms, name);
31-
if (matchKind !== MatchKind.none) {
32-
rawItems.push({ name, fileName, matchKind, declaration });
15+
var name = getDeclarationName(declaration);
16+
if (name !== undefined) {
17+
18+
// First do a quick check to see if the name of the declaration matches the
19+
// last portion of the (possibly) dotted name they're searching for.
20+
var matches = patternMatcher.getMatchesForLastSegmentOfPattern(name);
21+
22+
if (!matches) {
23+
continue;
24+
}
25+
26+
// It was a match! If the pattern has dots in it, then also see if hte
27+
// declaration container matches as well.
28+
if (patternMatcher.patternContainsDots) {
29+
var containers = getContainers(declaration);
30+
if (!containers) {
31+
return undefined;
32+
}
33+
34+
matches = patternMatcher.getMatches(containers, name);
35+
36+
if (!matches) {
37+
continue;
38+
}
39+
}
40+
41+
var fileName = sourceFile.fileName;
42+
var matchKind = bestMatchKind(matches);
43+
rawItems.push({ name, fileName, matchKind, isCaseSensitive: allMatchesAreCaseSensitive(matches), declaration });
3344
}
3445
}
3546
});
@@ -43,6 +54,129 @@ module ts.NavigateTo {
4354

4455
return items;
4556

57+
function allMatchesAreCaseSensitive(matches: PatternMatch[]): boolean {
58+
Debug.assert(matches.length > 0);
59+
60+
// This is a case sensitive match, only if all the submatches were case sensitive.
61+
for (var i = 0, n = matches.length; i < n; i++) {
62+
if (!matches[i].isCaseSensitive) {
63+
return false;
64+
}
65+
}
66+
67+
return true;
68+
}
69+
70+
function getDeclarationName(declaration: Declaration): string {
71+
var result = getTextOfIdentifierOrLiteral(declaration.name);
72+
if (result !== undefined) {
73+
return result;
74+
}
75+
76+
if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
77+
var expr = (<ComputedPropertyName>declaration.name).expression;
78+
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
79+
return (<PropertyAccessExpression>expr).name.text;
80+
}
81+
82+
return getTextOfIdentifierOrLiteral(expr);
83+
}
84+
85+
return undefined;
86+
}
87+
88+
function getTextOfIdentifierOrLiteral(node: Node) {
89+
if (node.kind === SyntaxKind.Identifier ||
90+
node.kind === SyntaxKind.StringLiteral ||
91+
node.kind === SyntaxKind.NumericLiteral) {
92+
93+
return (<Identifier | LiteralExpression>node).text;
94+
}
95+
96+
return undefined;
97+
}
98+
99+
function tryAddSingleDeclarationName(declaration: Declaration, containers: string[]) {
100+
if (declaration && declaration.name) {
101+
var text = getTextOfIdentifierOrLiteral(declaration.name);
102+
if (text !== undefined) {
103+
containers.unshift(text);
104+
}
105+
else if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
106+
return tryAddComputedPropertyName((<ComputedPropertyName>declaration.name).expression, containers, /*includeLastPortion:*/ true);
107+
}
108+
else {
109+
// Don't know how to add this.
110+
return false
111+
}
112+
}
113+
114+
return true;
115+
}
116+
117+
// Only added the names of computed properties if they're simple dotted expressions, like:
118+
//
119+
// [X.Y.Z]() { }
120+
function tryAddComputedPropertyName(expression: Expression, containers: string[], includeLastPortion: boolean): boolean {
121+
var text = getTextOfIdentifierOrLiteral(expression);
122+
if (text !== undefined) {
123+
if (includeLastPortion) {
124+
containers.unshift(text);
125+
}
126+
return true;
127+
}
128+
129+
if (expression.kind === SyntaxKind.PropertyAccessExpression) {
130+
var propertyAccess = <PropertyAccessExpression>expression;
131+
if (includeLastPortion) {
132+
containers.unshift(propertyAccess.name.text);
133+
}
134+
135+
return tryAddComputedPropertyName(propertyAccess.expression, containers, /*includeLastPortion:*/ true);
136+
}
137+
138+
return false;
139+
}
140+
141+
function getContainers(declaration: Declaration) {
142+
var containers: string[] = [];
143+
144+
// First, if we started with a computed property name, then add all but the last
145+
// portion into the container array.
146+
if (declaration.name.kind === SyntaxKind.ComputedPropertyName) {
147+
if (!tryAddComputedPropertyName((<ComputedPropertyName>declaration.name).expression, containers, /*includeLastPortion:*/ false)) {
148+
return undefined;
149+
}
150+
}
151+
152+
// Now, walk up our containers, adding all their names to the container array.
153+
declaration = getContainerNode(declaration);
154+
155+
while (declaration) {
156+
if (!tryAddSingleDeclarationName(declaration, containers)) {
157+
return undefined;
158+
}
159+
160+
declaration = getContainerNode(declaration);
161+
}
162+
163+
return containers;
164+
}
165+
166+
function bestMatchKind(matches: PatternMatch[]) {
167+
Debug.assert(matches.length > 0);
168+
var bestMatchKind = PatternMatchKind.camelCase;
169+
170+
for (var i = 0, n = matches.length; i < n; i++) {
171+
var kind = matches[i].kind;
172+
if (kind < bestMatchKind) {
173+
bestMatchKind = kind;
174+
}
175+
}
176+
177+
return bestMatchKind;
178+
}
179+
46180
// This means "compare in a case insensitive manner."
47181
var baseSensitivity: Intl.CollatorOptions = { sensitivity: "base" };
48182
function compareNavigateToItems(i1: RawNavigateToItem, i2: RawNavigateToItem) {
@@ -62,56 +196,14 @@ module ts.NavigateTo {
62196
name: rawItem.name,
63197
kind: getNodeKind(declaration),
64198
kindModifiers: getNodeModifiers(declaration),
65-
matchKind: MatchKind[rawItem.matchKind],
199+
matchKind: PatternMatchKind[rawItem.matchKind],
200+
isCaseSensitive: rawItem.isCaseSensitive,
66201
fileName: rawItem.fileName,
67202
textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()),
68203
// TODO(jfreeman): What should be the containerName when the container has a computed name?
69204
containerName: container && container.name ? (<Identifier>container.name).text : "",
70205
containerKind: container && container.name ? getNodeKind(container) : ""
71206
};
72207
}
73-
74-
function hasAnyUpperCaseCharacter(s: string): boolean {
75-
for (var i = 0, n = s.length; i < n; i++) {
76-
var c = s.charCodeAt(i);
77-
if ((CharacterCodes.A <= c && c <= CharacterCodes.Z) ||
78-
(c >= CharacterCodes.maxAsciiCharacter && s.charAt(i).toLocaleLowerCase() !== s.charAt(i))) {
79-
return true;
80-
}
81-
}
82-
83-
return false;
84-
}
85-
86-
function getMatchKind(searchTerms: { caseSensitive: boolean; term: string }[], name: string): MatchKind {
87-
var matchKind = MatchKind.none;
88-
89-
if (name) {
90-
for (var j = 0, n = searchTerms.length; j < n; j++) {
91-
var searchTerm = searchTerms[j];
92-
var nameToSearch = searchTerm.caseSensitive ? name : name.toLocaleLowerCase();
93-
// in case of case-insensitive search searchTerm.term will already be lower-cased
94-
var index = nameToSearch.indexOf(searchTerm.term);
95-
if (index < 0) {
96-
// Didn't match.
97-
return MatchKind.none;
98-
}
99-
100-
var termKind = MatchKind.substring;
101-
if (index === 0) {
102-
// here we know that match occur at the beginning of the string.
103-
// if search term and declName has the same length - we have an exact match, otherwise declName have longer length and this will be prefix match
104-
termKind = name.length === searchTerm.term.length ? MatchKind.exact : MatchKind.prefix;
105-
}
106-
107-
// Update our match kind if we don't have one, or if this match is better.
108-
if (matchKind === MatchKind.none || termKind < matchKind) {
109-
matchKind = termKind;
110-
}
111-
}
112-
}
113-
114-
return matchKind;
115-
}
116208
}
117209
}

0 commit comments

Comments
 (0)