patternMatcher: Return single best match instead of list (#23166)

This commit is contained in:
Andy 2018-04-11 15:35:22 -07:00 committed by GitHub
parent f6b206a75a
commit 28455c65b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 104 additions and 268 deletions

View File

@ -28,13 +28,11 @@ function walk(ctx: Lint.WalkContext<void>): void {
function shouldIgnoreCalledExpression(expression: ts.Expression): boolean {
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const methodName = (expression as ts.PropertyAccessExpression).name.text;
if (methodName.indexOf("set") === 0) {
if (methodName.startsWith("set") || methodName.startsWith("assert")) {
return true;
}
switch (methodName) {
case "apply":
case "assert":
case "assertEqual":
case "call":
case "equal":
case "fail":
@ -46,11 +44,10 @@ function walk(ctx: Lint.WalkContext<void>): void {
}
else if (expression.kind === ts.SyntaxKind.Identifier) {
const functionName = (expression as ts.Identifier).text;
if (functionName.indexOf("set") === 0) {
if (functionName.startsWith("set") || functionName.startsWith("assert")) {
return true;
}
switch (functionName) {
case "assert":
case "contains":
case "createAnonymousType":
case "createImportSpecifier":

View File

@ -1724,6 +1724,10 @@ namespace ts {
return compareComparableValues(a, b);
}
export function min<T>(a: T, b: T, compare: Comparer<T>): T {
return compare(a, b) === Comparison.LessThan ? a : b;
}
/**
* Compare two strings using a case-insensitive ordinal comparison.
*

View File

@ -95,251 +95,157 @@ describe("PatternMatcher", () => {
describe("SingleWordPattern", () => {
it("PreferCaseSensitiveExact", () => {
const match = getFirstMatch("Foo", "Foo");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("Foo", "Foo", { kind: ts.PatternMatchKind.exact, isCaseSensitive: true });
});
it("PreferCaseSensitiveExactInsensitive", () => {
const match = getFirstMatch("foo", "Foo");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("foo", "Foo", { kind: ts.PatternMatchKind.exact, isCaseSensitive: false });
});
it("PreferCaseSensitivePrefix", () => {
const match = getFirstMatch("Foo", "Fo");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("Foo", "Fo", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("PreferCaseSensitivePrefixCaseInsensitive", () => {
const match = getFirstMatch("Foo", "fo");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("Foo", "fo", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: false });
});
it("PreferCaseSensitiveCamelCaseMatchSimple", () => {
const match = getFirstMatch("FogBar", "FB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("FogBar", "FB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveCamelCaseMatchPartialPattern", () => {
const match = getFirstMatch("FogBar", "FoB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("FogBar", "FoB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveCamelCaseMatchToLongPattern1", () => {
const match = getFirstMatch("FogBar", "FBB");
assert.isTrue(match === undefined);
assertSegmentMatch("FogBar", "FBB", undefined);
});
it("PreferCaseSensitiveCamelCaseMatchToLongPattern2", () => {
const match = getFirstMatch("FogBar", "FoooB");
assert.isTrue(match === undefined);
assertSegmentMatch("FogBar", "FoooB", undefined);
});
it("CamelCaseMatchPartiallyUnmatched", () => {
const match = getFirstMatch("FogBarBaz", "FZ");
assert.isTrue(match === undefined);
assertSegmentMatch("FogBarBaz", "FZ", undefined);
});
it("CamelCaseMatchCompletelyUnmatched", () => {
const match = getFirstMatch("FogBarBaz", "ZZ");
assert.isTrue(match === undefined);
assertSegmentMatch("FogBarBaz", "ZZ", undefined);
});
it("TwoUppercaseCharacters", () => {
const match = getFirstMatch("SimpleUIElement", "SiUI");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("SimpleUIElement", "SiUI", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveLowercasePattern", () => {
const match = getFirstMatch("FogBar", "b");
assert.equal(ts.PatternMatchKind.substring, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("FogBar", "b", { kind: ts.PatternMatchKind.substring, isCaseSensitive: false });
});
it("PreferCaseSensitiveLowercasePattern2", () => {
const match = getFirstMatch("FogBar", "fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("FogBar", "fB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: false });
});
it("PreferCaseSensitiveTryUnderscoredName", () => {
const match = getFirstMatch("_fogBar", "_fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("_fogBar", "_fB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveTryUnderscoredName2", () => {
const match = getFirstMatch("_fogBar", "fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("_fogBar", "fB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveTryUnderscoredNameInsensitive", () => {
const match = getFirstMatch("_FogBar", "_fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("_FogBar", "_fB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: false });
});
it("PreferCaseSensitiveMiddleUnderscore", () => {
const match = getFirstMatch("Fog_Bar", "FB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("Fog_Bar", "FB", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveMiddleUnderscore2", () => {
const match = getFirstMatch("Fog_Bar", "F_B");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertSegmentMatch("Fog_Bar", "F_B", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("PreferCaseSensitiveMiddleUnderscore3", () => {
const match = getFirstMatch("Fog_Bar", "F__B");
assert.isTrue(undefined === match);
assertSegmentMatch("Fog_Bar", "F__B", undefined);
});
it("PreferCaseSensitiveMiddleUnderscore4", () => {
const match = getFirstMatch("Fog_Bar", "f_B");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("Fog_Bar", "f_B", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: false });
});
it("PreferCaseSensitiveMiddleUnderscore5", () => {
const match = getFirstMatch("Fog_Bar", "F_b");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
assertSegmentMatch("Fog_Bar", "F_b", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: false });
});
it("AllLowerPattern1", () => {
const match = getFirstMatch("FogBarChangedEventArgs", "changedeventargs");
assert.isTrue(undefined !== match);
assertSegmentMatch("FogBarChangedEventArgs", "changedeventargs", { kind: ts.PatternMatchKind.substring, isCaseSensitive: false });
});
it("AllLowerPattern2", () => {
const match = getFirstMatch("FogBarChangedEventArgs", "changedeventarrrgh");
assert.isTrue(undefined === match);
assertSegmentMatch("FogBarChangedEventArgs", "changedeventarrrgh", undefined);
});
it("AllLowerPattern3", () => {
const match = getFirstMatch("ABCDEFGH", "bcd");
assert.isTrue(undefined !== match);
assertSegmentMatch("ABCDEFGH", "bcd", { kind: ts.PatternMatchKind.substring, isCaseSensitive: false });
});
it("AllLowerPattern4", () => {
const match = getFirstMatch("AbcdefghijEfgHij", "efghij");
assert.isTrue(undefined === match);
assertSegmentMatch("AbcdefghijEfgHij", "efghij", undefined);
});
});
describe("MultiWordPattern", () => {
it("ExactWithLowercase", () => {
const matches = getAllMatches("AddMetadataReference", "addmetadatareference");
assertContainsKind(ts.PatternMatchKind.exact, matches);
assertSegmentMatch("AddMetadataReference", "addmetadatareference", { kind: ts.PatternMatchKind.exact, isCaseSensitive: false });
});
it("SingleLowercasedSearchWord1", () => {
const matches = getAllMatches("AddMetadataReference", "add");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertSegmentMatch("AddMetadataReference", "add", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: false });
});
it("SingleLowercasedSearchWord2", () => {
const matches = getAllMatches("AddMetadataReference", "metadata");
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "metadata", { kind: ts.PatternMatchKind.substring, isCaseSensitive: false });
});
it("SingleUppercaseSearchWord1", () => {
const matches = getAllMatches("AddMetadataReference", "Add");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertSegmentMatch("AddMetadataReference", "Add", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("SingleUppercaseSearchWord2", () => {
const matches = getAllMatches("AddMetadataReference", "Metadata");
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "Metadata", { kind: ts.PatternMatchKind.substring, isCaseSensitive: true });
});
it("SingleUppercaseSearchLetter1", () => {
const matches = getAllMatches("AddMetadataReference", "A");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertSegmentMatch("AddMetadataReference", "A", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("SingleUppercaseSearchLetter2", () => {
const matches = getAllMatches("AddMetadataReference", "M");
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "M", { kind: ts.PatternMatchKind.substring, isCaseSensitive: true });
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "add metadata");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
it("TwoLowercaseWords0", () => {
assertSegmentMatch("AddMetadataReference", "add metadata", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: false });
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "A M");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
it("TwoLowercaseWords1", () => {
assertSegmentMatch("AddMetadataReference", "A M", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "AM");
assertContainsKind(ts.PatternMatchKind.camelCase, matches);
it("TwoLowercaseWords2", () => {
assertSegmentMatch("AddMetadataReference", "AM", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "ref Metadata");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
it("TwoLowercaseWords3", () => {
assertSegmentMatch("AddMetadataReference", "ref Metadata", { kind: ts.PatternMatchKind.substring, isCaseSensitive: true });
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "ref M");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
it("TwoLowercaseWords4", () => {
assertSegmentMatch("AddMetadataReference", "ref M", { kind: ts.PatternMatchKind.substring, isCaseSensitive: true });
});
it("MixedCamelCase", () => {
const matches = getAllMatches("AddMetadataReference", "AMRe");
assertContainsKind(ts.PatternMatchKind.camelCase, matches);
assertSegmentMatch("AddMetadataReference", "AMRe", { kind: ts.PatternMatchKind.camelCase, isCaseSensitive: true });
});
it("BlankPattern", () => {
@ -351,120 +257,83 @@ describe("PatternMatcher", () => {
});
it("EachWordSeparately1", () => {
const matches = getAllMatches("AddMetadataReference", "add Meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "add Meta", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: false });
});
it("EachWordSeparately2", () => {
const matches = getAllMatches("AddMetadataReference", "Add meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "Add meta", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("EachWordSeparately3", () => {
const matches = getAllMatches("AddMetadataReference", "Add Meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
assertSegmentMatch("AddMetadataReference", "Add Meta", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("MixedCasing", () => {
const matches = getAllMatches("AddMetadataReference", "mEta");
assert.isTrue(matches === undefined);
assertSegmentMatch("AddMetadataReference", "mEta", undefined);
});
it("MixedCasing2", () => {
const matches = getAllMatches("AddMetadataReference", "Data");
assert.isTrue(matches === undefined);
assertSegmentMatch("AddMetadataReference", "Data", undefined);
});
it("AsteriskSplit", () => {
const matches = getAllMatches("GetKeyWord", "K*W");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
assertSegmentMatch("GetKeyWord", "K*W", { kind: ts.PatternMatchKind.substring, isCaseSensitive: true });
});
it("LowercaseSubstring1", () => {
const matches = getAllMatches("Operator", "a");
assert.isTrue(matches === undefined);
assertSegmentMatch("Operator", "a", undefined);
});
it("LowercaseSubstring2", () => {
const matches = getAllMatches("FooAttribute", "a");
assertContainsKind(ts.PatternMatchKind.substring, matches);
assert.isFalse(matches[0].isCaseSensitive);
assertSegmentMatch("FooAttribute", "a", { kind: ts.PatternMatchKind.substring, isCaseSensitive: false });
});
});
describe("DottedPattern", () => {
it("DottedPattern1", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "B.Q");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
assertFullMatch("Foo.Bar.Baz", "Quux", "B.Q", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("DottedPattern2", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "C.Q");
assert.isTrue(match === undefined);
assertFullMatch("Foo.Bar.Baz", "Quux", "C.Q", undefined);
});
it("DottedPattern3", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "B.B.Q");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
assertFullMatch("Foo.Bar.Baz", "Quux", "B.B.Q", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("DottedPattern4", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "Baz.Quux");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
assertFullMatch("Foo.Bar.Baz", "Quux", "Baz.Quux", { kind: ts.PatternMatchKind.exact, isCaseSensitive: true });
});
it("DottedPattern5", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "F.B.B.Quux");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
assertFullMatch("Foo.Bar.Baz", "Quux", "F.B.B.Quux", { kind: ts.PatternMatchKind.prefix, isCaseSensitive: true });
});
it("DottedPattern6", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "F.F.B.B.Quux");
assert.isTrue(match === undefined);
assertFullMatch("Foo.Bar.Baz", "Quux", "F.F.B.B.Quux", undefined);
});
it("DottedPattern7", () => {
let match = getFirstMatch("UIElement", "UIElement");
match = getFirstMatch("GetKeyword", "UIElement");
assert.isTrue(match === undefined);
assertSegmentMatch("UIElement", "UIElement", { kind: ts.PatternMatchKind.exact, isCaseSensitive: true });
assertSegmentMatch("GetKeyword", "UIElement", undefined);
});
});
function assertSegmentMatch(candidate: string, pattern: string, expected: ts.PatternMatch | undefined): void {
assert.deepEqual(ts.createPatternMatcher(pattern).getMatchForLastSegmentOfPattern(candidate), expected);
}
function assertInvalidPattern(pattern: string) {
assert.equal(ts.createPatternMatcher(pattern), undefined);
}
function getFirstMatch(candidate: string, pattern: string): ts.PatternMatch {
const matches = ts.createPatternMatcher(pattern).getMatchesForLastSegmentOfPattern(candidate);
return matches ? matches[0] : undefined;
}
function getAllMatches(candidate: string, pattern: string): ts.PatternMatch[] {
return ts.createPatternMatcher(pattern).getMatchesForLastSegmentOfPattern(candidate);
}
function getFirstMatchForDottedPattern(dottedContainer: string, candidate: string, pattern: string): ts.PatternMatch {
const matches = ts.createPatternMatcher(pattern).getMatches(dottedContainer.split("."), candidate);
return matches ? matches[0] : undefined;
function assertFullMatch(dottedContainer: string, candidate: string, pattern: string, expected: ts.PatternMatch | undefined): void {
assert.deepEqual(ts.createPatternMatcher(pattern).getFullMatch(dottedContainer.split("."), candidate), expected);
}
function spanListToSubstrings(identifier: string, spans: ts.TextSpan[]) {
return ts.map(spans, s => identifier.substr(s.start, s.length));
return spans.map(s => identifier.substr(s.start, s.length));
}
function breakIntoCharacterSpans(identifier: string) {
@ -474,23 +343,12 @@ describe("PatternMatcher", () => {
function breakIntoWordSpans(identifier: string) {
return spanListToSubstrings(identifier, ts.breakIntoWordSpans(identifier));
}
function assertArrayEquals<T>(array1: T[], array2: T[]) {
assert.equal(array1.length, array2.length);
for (let i = 0; i < array1.length; i++) {
assert.equal(array1[i], array2[i]);
}
}
function verifyBreakIntoCharacterSpans(original: string, ...parts: string[]): void {
assertArrayEquals(parts, breakIntoCharacterSpans(original));
assert.deepEqual(parts, breakIntoCharacterSpans(original));
}
function verifyBreakIntoWordSpans(original: string, ...parts: string[]): void {
assertArrayEquals(parts, breakIntoWordSpans(original));
}
function assertContainsKind(kind: ts.PatternMatchKind, results: ts.PatternMatch[]) {
assert.isTrue(ts.forEach(results, r => r.kind === kind));
assert.deepEqual(parts, breakIntoWordSpans(original));
}
});

View File

@ -36,28 +36,24 @@ namespace ts.NavigateTo {
function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: ReadonlyArray<Declaration>, checker: TypeChecker, fileName: string, rawItems: Push<RawNavigateToItem>): void {
// First do a quick check to see if the name of the declaration matches the
// last portion of the (possibly) dotted name they're searching for.
const matches = patternMatcher.getMatchesForLastSegmentOfPattern(name);
if (!matches) {
const match = patternMatcher.getMatchForLastSegmentOfPattern(name);
if (!match) {
return; // continue to next named declarations
}
for (const declaration of declarations) {
if (!shouldKeepItem(declaration, checker)) {
continue;
}
if (!shouldKeepItem(declaration, checker)) continue;
// It was a match! If the pattern has dots in it, then also see if the
// declaration container matches as well.
let containerMatches = matches;
if (patternMatcher.patternContainsDots) {
containerMatches = patternMatcher.getMatches(getContainers(declaration), name);
if (!containerMatches) {
continue;
const fullMatch = patternMatcher.getFullMatch(getContainers(declaration), name);
if (fullMatch) {
rawItems.push({ name, fileName, matchKind: fullMatch.kind, isCaseSensitive: fullMatch.isCaseSensitive, declaration });
}
}
rawItems.push({ name, fileName, matchKind: Math.min(...matches.map(m => m.kind)), isCaseSensitive: matches.every(m => m.isCaseSensitive), declaration });
else {
// If the pattern has dots in it, then also see if the declaration container matches as well.
rawItems.push({ name, fileName, matchKind: match.kind, isCaseSensitive: match.isCaseSensitive, declaration });
}
}
}

View File

@ -33,12 +33,12 @@ namespace ts {
// this will return a successful match, having only tested "SK" against "SyntaxKind". At
// that point a call can be made to 'getMatches("SyntaxKind", "ts.compiler")', with the
// work to create 'ts.compiler' only being done once the first match succeeded.
getMatchesForLastSegmentOfPattern(candidate: string): PatternMatch[];
getMatchForLastSegmentOfPattern(candidate: string): PatternMatch | undefined;
// Fully checks a candidate, with an dotted container, against the search pattern.
// The candidate must match the last part of the search pattern, and the dotted container
// must match the preceding segments of the pattern.
getMatches(candidateContainers: string[], candidate: string): PatternMatch[] | undefined;
getFullMatch(candidateContainers: ReadonlyArray<string>, candidate: string): PatternMatch | undefined;
// Whether or not the pattern contained dots or not. Clients can use this to determine
// If they should call getMatches, or if getMatchesForLastSegmentOfPattern is sufficient.
@ -109,13 +109,13 @@ namespace ts {
if (dotSeparatedSegments.some(segment => !segment.subWordTextChunks.length)) return undefined;
return {
getMatches: (containers, candidate) => getMatches(containers, candidate, dotSeparatedSegments, stringToWordSpans),
getMatchesForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans),
getFullMatch: (containers, candidate) => getFullMatch(containers, candidate, dotSeparatedSegments, stringToWordSpans),
getMatchForLastSegmentOfPattern: candidate => matchSegment(candidate, last(dotSeparatedSegments), stringToWordSpans),
patternContainsDots: dotSeparatedSegments.length > 1
};
}
function getMatches(candidateContainers: ReadonlyArray<string>, candidate: string, dotSeparatedSegments: ReadonlyArray<Segment>, stringToWordSpans: Map<TextSpan[]>): PatternMatch[] | undefined {
function getFullMatch(candidateContainers: ReadonlyArray<string>, candidate: string, dotSeparatedSegments: ReadonlyArray<Segment>, stringToWordSpans: Map<TextSpan[]>): PatternMatch | undefined {
// First, check that the last part of the dot separated pattern matches the name of the
// candidate. If not, then there's no point in proceeding and doing the more
// expensive work.
@ -134,29 +134,13 @@ namespace ts {
return undefined;
}
// So far so good. Now break up the container for the candidate and check if all
// the dotted parts match up correctly.
const totalMatch = candidateMatch;
let bestMatch: PatternMatch | undefined;
for (let i = dotSeparatedSegments.length - 2, j = candidateContainers.length - 1;
i >= 0;
i -= 1, j -= 1) {
const segment = dotSeparatedSegments[i];
const containerName = candidateContainers[j];
const containerMatch = matchSegment(containerName, segment, stringToWordSpans);
if (!containerMatch) {
// This container didn't match the pattern piece. So there's no match at all.
return undefined;
}
addRange(totalMatch, containerMatch);
bestMatch = betterMatch(bestMatch, matchSegment(candidateContainers[j], dotSeparatedSegments[i], stringToWordSpans));
}
// Success, this symbol's full name matched against the dotted name the user was asking
// about.
return totalMatch;
return bestMatch;
}
function getWordSpans(word: string, stringToWordSpans: Map<TextSpan[]>): TextSpan[] {
@ -219,7 +203,7 @@ namespace ts {
}
}
function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map<TextSpan[]>): PatternMatch[] {
function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map<TextSpan[]>): PatternMatch {
// First check if the segment matches as is. This is also useful if the segment contains
// characters we would normally strip when splitting into parts that we also may want to
// match in the candidate. For example if the segment is "@int" and the candidate is
@ -229,9 +213,7 @@ namespace ts {
// multi-word segment.
if (every(segment.totalTextChunk.text, ch => ch !== CharacterCodes.space && ch !== CharacterCodes.asterisk)) {
const match = matchTextChunk(candidate, segment.totalTextChunk, stringToWordSpans);
if (match) {
return [match];
}
if (match) return match;
}
// The logic for pattern matching is now as follows:
@ -271,20 +253,19 @@ namespace ts {
// Only if all words have some sort of match is the pattern considered matched.
const subWordTextChunks = segment.subWordTextChunks;
let matches: PatternMatch[];
let bestMatch: PatternMatch | undefined;
for (const subWordTextChunk of subWordTextChunks) {
// Try to match the candidate with this word
const result = matchTextChunk(candidate, subWordTextChunk, stringToWordSpans);
if (!result) {
return undefined;
}
matches = matches || [];
matches.push(result);
bestMatch = betterMatch(bestMatch, matchTextChunk(candidate, subWordTextChunk, stringToWordSpans));
}
return bestMatch;
}
return matches;
function betterMatch(a: PatternMatch | undefined, b: PatternMatch | undefined): PatternMatch {
return min(a, b, compareMatches);
}
function compareMatches(a: PatternMatch | undefined, b: PatternMatch | undefined): Comparison {
return a === undefined ? Comparison.GreaterThan : b === undefined ? Comparison.LessThan
: compareValues(a.kind, b.kind) || compareBooleans(!a.isCaseSensitive, !b.isCaseSensitive);
}
function partStartsWith(candidate: string, candidateSpan: TextSpan, pattern: string, ignoreCase: boolean, patternSpan: TextSpan = { start: 0, length: pattern.length }): boolean {