Skip to content

Commit 5b928a3

Browse files
author
Andy
authored
patternMatcher: Use helper functions to simplify loops over strings (microsoft#23095)
* patternMatcher: Use helper functions to simplify loops over strings * Fix bug
1 parent 4aeb295 commit 5b928a3

1 file changed

Lines changed: 46 additions & 106 deletions

File tree

src/services/patternMatcher.ts

Lines changed: 46 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,6 @@ namespace ts {
225225
}
226226
}
227227

228-
function containsSpaceOrAsterisk(text: string): boolean {
229-
for (let i = 0; i < text.length; i++) {
230-
const ch = text.charCodeAt(i);
231-
if (ch === CharacterCodes.space || ch === CharacterCodes.asterisk) {
232-
return true;
233-
}
234-
}
235-
236-
return false;
237-
}
238-
239228
function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map<TextSpan[]>): PatternMatch[] {
240229
// First check if the segment matches as is. This is also useful if the segment contains
241230
// characters we would normally strip when splitting into parts that we also may want to
@@ -244,7 +233,7 @@ namespace ts {
244233
//
245234
// Note: if the segment contains a space or an asterisk then we must assume that it's a
246235
// multi-word segment.
247-
if (!containsSpaceOrAsterisk(segment.totalTextChunk.text)) {
236+
if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) {
248237
const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans);
249238
if (match) {
250239
return [match];
@@ -304,35 +293,13 @@ namespace ts {
304293
return matches;
305294
}
306295

307-
function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan?: TextSpan): boolean {
308-
const patternPartStart = patternSpan ? patternSpan.start : 0;
309-
const patternPartLength = patternSpan ? patternSpan.length : pattern.length;
310-
311-
if (patternPartLength > candidateSpan.length) {
312-
// Pattern part is longer than the candidate part. There can never be a match.
313-
return false;
314-
}
315-
316-
if (ignoreCase) {
317-
for (let i = 0; i < patternPartLength; i++) {
318-
const ch1 = pattern.charCodeAt(patternPartStart + i);
319-
const ch2 = candidate.charCodeAt(candidateSpan.start + i);
320-
if (toLowerCase(ch1) !== toLowerCase(ch2)) {
321-
return false;
322-
}
323-
}
324-
}
325-
else {
326-
for (let i = 0; i < patternPartLength; i++) {
327-
const ch1 = pattern.charCodeAt(patternPartStart + i);
328-
const ch2 = candidate.charCodeAt(candidateSpan.start + i);
329-
if (ch1 !== ch2) {
330-
return false;
331-
}
332-
}
333-
}
296+
function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean {
297+
return patternSpan.length <= candidateSpan.length // If pattern part is longer than the candidate part there can never be a match.
298+
&& everyInRange(0, patternSpan.length, i => equalChars(pattern.charCodeAt(patternSpan.start + i), candidate.charCodeAt(candidateSpan.start + i), ignoreCase));
299+
}
334300

335-
return true;
301+
function equalChars(ch1: number, ch2: number, ignoreCase: boolean): boolean {
302+
return ignoreCase ? toLowerCase(ch1) === toLowerCase(ch2) : ch1 === ch2;
336303
}
337304

338305
function tryCamelCaseMatch(candidate: string, candidateParts: TextSpan[], chunk: TextChunk, ignoreCase: boolean): boolean {
@@ -455,29 +422,15 @@ namespace ts {
455422
// Assumes 'value' is already lowercase.
456423
function indexOfIgnoringCase(str: string, value: string): number {
457424
const n = str.length - value.length;
458-
for (let i = 0; i <= n; i++) {
459-
if (startsWithIgnoringCase(str, value, i)) {
460-
return i;
425+
for (let start = 0; start <= n; start++) {
426+
if (every(value, (valueChar, i) => toLowerCase(str.charCodeAt(i + start)) === valueChar)) {
427+
return start;
461428
}
462429
}
463430

464431
return -1;
465432
}
466433

467-
// Assumes 'value' is already lowercase.
468-
function startsWithIgnoringCase(str: string, value: string, start: number): boolean {
469-
for (let i = 0; i < value.length; i++) {
470-
const ch1 = toLowerCase(str.charCodeAt(i + start));
471-
const ch2 = value.charCodeAt(i);
472-
473-
if (ch1 !== ch2) {
474-
return false;
475-
}
476-
}
477-
478-
return true;
479-
}
480-
481434
function toLowerCase(ch: number): number {
482435
// Fast convert for the ascii range.
483436
if (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) {
@@ -557,7 +510,7 @@ namespace ts {
557510
const currentIsDigit = isDigit(identifier.charCodeAt(i));
558511

559512
const hasTransitionFromLowerToUpper = transitionFromLowerToUpper(identifier, word, i);
560-
const hasTransitionFromUpperToLower = transitionFromUpperToLower(identifier, word, i, wordStart);
513+
const hasTransitionFromUpperToLower = word && transitionFromUpperToLower(identifier, i, wordStart);
561514

562515
if (charIsPunctuation(identifier.charCodeAt(i - 1)) ||
563516
charIsPunctuation(identifier.charCodeAt(i)) ||
@@ -612,52 +565,29 @@ namespace ts {
612565
}
613566

614567
function isAllPunctuation(identifier: string, start: number, end: number): boolean {
615-
for (let i = start; i < end; i++) {
616-
const ch = identifier.charCodeAt(i);
617-
618-
// We don't consider _ or $ as punctuation as there may be things with that name.
619-
if (!charIsPunctuation(ch) || ch === CharacterCodes._ || ch === CharacterCodes.$) {
620-
return false;
621-
}
622-
}
623-
624-
return true;
568+
return every(identifier, ch => charIsPunctuation(ch) && ch !== CharacterCodes._, start, end);
625569
}
626570

627-
function transitionFromUpperToLower(identifier: string, word: boolean, index: number, wordStart: number): boolean {
628-
if (word) {
629-
// Cases this supports:
630-
// 1) IDisposable -> I, Disposable
631-
// 2) UIElement -> UI, Element
632-
// 3) HTMLDocument -> HTML, Document
633-
//
634-
// etc.
635-
if (index !== wordStart &&
636-
index + 1 < identifier.length) {
637-
const currentIsUpper = isUpperCaseLetter(identifier.charCodeAt(index));
638-
const nextIsLower = isLowerCaseLetter(identifier.charCodeAt(index + 1));
639-
640-
if (currentIsUpper && nextIsLower) {
641-
// We have a transition from an upper to a lower letter here. But we only
642-
// want to break if all the letters that preceded are uppercase. i.e. if we
643-
// have "Foo" we don't want to break that into "F, oo". But if we have
644-
// "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI,
645-
// Foo". i.e. the last uppercase letter belongs to the lowercase letters
646-
// that follows. Note: this will make the following not split properly:
647-
// "HELLOthere". However, these sorts of names do not show up in .Net
648-
// programs.
649-
for (let i = wordStart; i < index; i++) {
650-
if (!isUpperCaseLetter(identifier.charCodeAt(i))) {
651-
return false;
652-
}
653-
}
654-
655-
return true;
656-
}
657-
}
658-
}
659-
660-
return false;
571+
function transitionFromUpperToLower(identifier: string, index: number, wordStart: number): boolean {
572+
// Cases this supports:
573+
// 1) IDisposable -> I, Disposable
574+
// 2) UIElement -> UI, Element
575+
// 3) HTMLDocument -> HTML, Document
576+
//
577+
// etc.
578+
// We have a transition from an upper to a lower letter here. But we only
579+
// want to break if all the letters that preceded are uppercase. i.e. if we
580+
// have "Foo" we don't want to break that into "F, oo". But if we have
581+
// "IFoo" or "UIFoo", then we want to break that into "I, Foo" and "UI,
582+
// Foo". i.e. the last uppercase letter belongs to the lowercase letters
583+
// that follows. Note: this will make the following not split properly:
584+
// "HELLOthere". However, these sorts of names do not show up in .Net
585+
// programs.
586+
return index !== wordStart
587+
&& index + 1 < identifier.length
588+
&& isUpperCaseLetter(identifier.charCodeAt(index))
589+
&& isLowerCaseLetter(identifier.charCodeAt(index + 1))
590+
&& every(identifier, isUpperCaseLetter, wordStart, index);
661591
}
662592

663593
function transitionFromLowerToUpper(identifier: string, word: boolean, index: number): boolean {
@@ -677,9 +607,19 @@ namespace ts {
677607
// on characters would be: A M
678608
//
679609
// We break the search string on characters. But we break the symbol name on words.
680-
const transition = word
681-
? (currentIsUpper && !lastIsUpper)
682-
: currentIsUpper;
683-
return transition;
610+
return currentIsUpper && (!word || !lastIsUpper);
611+
}
612+
613+
function everyInRange(start: number, end: number, pred: (n: number) => boolean): boolean {
614+
for (let i = start; i < end; i++) {
615+
if (!pred(i)) {
616+
return false;
617+
}
618+
}
619+
return true;
620+
}
621+
622+
function every(s: string, pred: (ch: number, index: number) => boolean, start = 0, end = s.length): boolean {
623+
return everyInRange(start, end, i => pred(s.charCodeAt(i), i));
684624
}
685625
}

0 commit comments

Comments
 (0)