diff --git a/scripts/tslint/rules/booleanTriviaRule.ts b/scripts/tslint/rules/booleanTriviaRule.ts index dbfdc28438e..0224a0f08d8 100644 --- a/scripts/tslint/rules/booleanTriviaRule.ts +++ b/scripts/tslint/rules/booleanTriviaRule.ts @@ -28,13 +28,11 @@ function walk(ctx: Lint.WalkContext): 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 { } 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": diff --git a/src/compiler/core.ts b/src/compiler/core.ts index b138156dfd5..c4885e9d683 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1724,6 +1724,10 @@ namespace ts { return compareComparableValues(a, b); } + export function min(a: T, b: T, compare: Comparer): T { + return compare(a, b) === Comparison.LessThan ? a : b; + } + /** * Compare two strings using a case-insensitive ordinal comparison. * diff --git a/src/harness/unittests/services/patternMatcher.ts b/src/harness/unittests/services/patternMatcher.ts index 5aee5d2f4eb..64de3557da4 100644 --- a/src/harness/unittests/services/patternMatcher.ts +++ b/src/harness/unittests/services/patternMatcher.ts @@ -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(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)); } }); diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts index 4713f89dbde..84c299abf61 100644 --- a/src/services/navigateTo.ts +++ b/src/services/navigateTo.ts @@ -36,28 +36,24 @@ namespace ts.NavigateTo { function getItemsFromNamedDeclaration(patternMatcher: PatternMatcher, name: string, declarations: ReadonlyArray, checker: TypeChecker, fileName: string, rawItems: Push): 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 }); + } } } diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index 84ef1244040..2d26d059021 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -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, 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, candidate: string, dotSeparatedSegments: ReadonlyArray, stringToWordSpans: Map): PatternMatch[] | undefined { + function getFullMatch(candidateContainers: ReadonlyArray, candidate: string, dotSeparatedSegments: ReadonlyArray, stringToWordSpans: Map): 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[] { @@ -219,7 +203,7 @@ namespace ts { } } - function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map): PatternMatch[] { + function matchSegment(candidate: string, segment: Segment, stringToWordSpans: Map): 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 {