1
1
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 } ;
17
3
4
+ export function getNavigateToItems ( program : Program , cancellationToken : CancellationTokenObject , searchValue : string , maxResultCount : number ) : NavigateToItem [ ] {
5
+ var patternMatcher = createPatternMatcher ( searchValue ) ;
18
6
var rawItems : RawNavigateToItem [ ] = [ ] ;
19
7
20
8
// Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[]
21
9
forEach ( program . getSourceFiles ( ) , sourceFile => {
22
10
cancellationToken . throwIfCancellationRequested ( ) ;
23
11
24
- var fileName = sourceFile . fileName ;
25
12
var declarations = sourceFile . getNamedDeclarations ( ) ;
26
13
for ( var i = 0 , n = declarations . length ; i < n ; i ++ ) {
27
14
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 } ) ;
33
44
}
34
45
}
35
46
} ) ;
@@ -43,6 +54,129 @@ module ts.NavigateTo {
43
54
44
55
return items ;
45
56
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
+
46
180
// This means "compare in a case insensitive manner."
47
181
var baseSensitivity : Intl . CollatorOptions = { sensitivity : "base" } ;
48
182
function compareNavigateToItems ( i1 : RawNavigateToItem , i2 : RawNavigateToItem ) {
@@ -62,56 +196,14 @@ module ts.NavigateTo {
62
196
name : rawItem . name ,
63
197
kind : getNodeKind ( declaration ) ,
64
198
kindModifiers : getNodeModifiers ( declaration ) ,
65
- matchKind : MatchKind [ rawItem . matchKind ] ,
199
+ matchKind : PatternMatchKind [ rawItem . matchKind ] ,
200
+ isCaseSensitive : rawItem . isCaseSensitive ,
66
201
fileName : rawItem . fileName ,
67
202
textSpan : createTextSpanFromBounds ( declaration . getStart ( ) , declaration . getEnd ( ) ) ,
68
203
// TODO(jfreeman): What should be the containerName when the container has a computed name?
69
204
containerName : container && container . name ? ( < Identifier > container . name ) . text : "" ,
70
205
containerKind : container && container . name ? getNodeKind ( container ) : ""
71
206
} ;
72
207
}
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
- }
116
208
}
117
209
}
0 commit comments