11module 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