Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 58c9371

Browse files
committed
- Removed 'goto' from 'LineFormatter', straighten case breaks
- Added TextEditCollectionAssertions - Various miscellaneous code clean-ups
1 parent 7dbeffa commit 58c9371

8 files changed

Lines changed: 259 additions & 194 deletions

File tree

src/Analysis/Engine/Impl/Infrastructure/Extensions/StringBuilderExtensions.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ public static StringBuilder TrimEnd(this StringBuilder sb) {
2525
return sb;
2626
}
2727

28-
public static StringBuilder EnsureEndsWithSpace(this StringBuilder sb, int count = 1, bool allowLeading = false) {
29-
if (sb.Length == 0 && !allowLeading) {
28+
public static StringBuilder EnsureEndsWithWhiteSpace(this StringBuilder sb, int whiteSpaceCount = 1) {
29+
if (sb.Length == 0) {
3030
return sb;
3131
}
3232

33-
for (var i = sb.Length - 1; i >= 0 && char.IsWhiteSpace(sb[i]); i--) {
34-
count--;
33+
for (var i = sb.Length - 1; i >= 0 && whiteSpaceCount > 0 && char.IsWhiteSpace(sb[i]); i--) {
34+
whiteSpaceCount--;
3535
}
3636

37-
if (count > 0) {
38-
sb.Append(new string(' ', count));
37+
if (whiteSpaceCount > 0) {
38+
sb.Append(' ', whiteSpaceCount);
3939
}
40-
40+
4141
return sb;
4242
}
4343
}

src/Analysis/Engine/Test/FluentAssertions/AssertionsFactory.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,8 @@ public static SignatureHelpAssertions Should(this SignatureHelp signatureHelp)
102102

103103
public static SignatureInformationAssertions Should(this SignatureInformation signatureInformation)
104104
=> new SignatureInformationAssertions(signatureInformation);
105+
106+
public static TextEditCollectionAssertions Should(this IEnumerable<TextEdit> textEdits)
107+
=> new TextEditCollectionAssertions(textEdits);
105108
}
106109
}

src/Analysis/Engine/Test/FluentAssertions/AssertionsUtilities.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ public static string GetName(object value) {
186186
return scope.Name;
187187
case IAnalysisValue analysisValue:
188188
return $"value {analysisValue.Name}";
189+
case Range range:
190+
return $"({range.start.line}, {range.start.character}) - ({range.end.line}, {range.end.character})";
191+
case Position position:
192+
return $"({position.line}, {position.character})";
189193
case string str:
190194
return str;
191195
default:
@@ -205,6 +209,9 @@ public static IEnumerable<IAnalysisValue> FlattenAnalysisValues(IEnumerable<IAna
205209
}
206210
}
207211

212+
public static bool RangeEquals(Range r1, Range r2) => PositionEquals(r1.start, r2.start) && PositionEquals(r1.end, r2.end);
213+
public static bool PositionEquals(Position p1, Position p2) => p1.line == p2.line && p1.character == p2.character;
214+
208215
public static string DoubleEscape(string input)
209216
=> input.Replace("\r", "\\\u200Br").Replace("\n", "\\\u200Bn").Replace("\t", @"\t");
210217
}

src/Analysis/Engine/Test/FluentAssertions/ReferenceCollectionAssertions.cs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ public AndConstraint<ReferenceCollectionAssertions> OnlyHaveReferences(IEnumerab
7575
if (excess.Length > 0) {
7676
var excessString = string.Join(", ", excess.Select(Format));
7777
var errorMessage = expected.Length > 1
78-
? $"Expected {GetName()} to have only {expected.Length} references{{reason}}, but it also has references: {excessString}."
78+
? $"Expected {GetSubjectName()} to have only {expected.Length} references{{reason}}, but it also has references: {excessString}."
7979
: expected.Length > 0
80-
? $"Expected {GetName()} to have only one reference{{reason}}, but it also has references: {excessString}."
81-
: $"Expected {GetName()} to have no references{{reason}}, but it has references: {excessString}.";
80+
? $"Expected {GetSubjectName()} to have only one reference{{reason}}, but it also has references: {excessString}."
81+
: $"Expected {GetSubjectName()} to have no references{{reason}}, but it has references: {excessString}.";
8282

8383
Execute.Assertion.BecauseOf(because, reasonArgs).FailWith(errorMessage);
8484
}
@@ -87,10 +87,7 @@ public AndConstraint<ReferenceCollectionAssertions> OnlyHaveReferences(IEnumerab
8787
}
8888

8989
private static string Format((Uri uri, (int, int, int, int) range, ReferenceKind? kind) reference)
90-
=> $"({TestData.GetTestRelativePath(reference.uri)}, {Format(reference.range)}, {reference.kind})";
91-
92-
private static string Format((int startLine, int startCharacter, int endLine, int endCharacter) range)
93-
=> $"({range.startLine}, {range.startCharacter}) - ({range.endLine}, {range.endCharacter})";
90+
=> $"({TestData.GetTestRelativePath(reference.uri)}, {GetName(reference.range)}, {reference.kind})";
9491

9592
[CustomAssertion]
9693
public AndConstraint<ReferenceCollectionAssertions> HaveReferenceAt(Uri documentUri, int startLine, int startCharacter, int endLine, int endCharacter, ReferenceKind? referenceKind = null, string because = "", params object[] reasonArgs) {
@@ -117,38 +114,29 @@ public AndConstraint<ReferenceCollectionAssertions> HaveReferenceAt(Uri document
117114
private string FindReference(Uri documentUri, string moduleName, Range range, ReferenceKind? referenceKind = null) {
118115
var candidates = Subject.Where(av => Equals(av.uri, documentUri)).ToArray();
119116
if (candidates.Length == 0) {
120-
return $"Expected {GetName()} to have reference in the module '{moduleName}'{{reason}}, but no references has been found.";
117+
return $"Expected {GetSubjectName()} to have reference in the module '{moduleName}'{{reason}}, but no references has been found.";
121118
}
122119

123120
foreach (var candidate in candidates.Where(c => RangeEquals(c.range, range))) {
124121
return referenceKind.HasValue && candidate._kind != referenceKind
125-
? $"Expected {GetName()} to have reference of type '{referenceKind}'{{reason}}, but reference in module '{moduleName}' at {RangeToString(range)} has type '{candidate._kind}'"
122+
? $"Expected {GetSubjectName()} to have reference of type '{referenceKind}'{{reason}}, but reference in module '{moduleName}' at {GetName(range)} has type '{candidate._kind}'"
126123
: string.Empty;
127124
}
128125

129-
var errorMessage = $"Expected {GetName()} to have reference at {RangeToString(range)}{{reason}}, but module '{moduleName}' has no references at that range.";
126+
var errorMessage = $"Expected {GetSubjectName()} to have reference at {GetName(range)}{{reason}}, but module '{moduleName}' has no references at that range.";
130127
if (!referenceKind.HasValue) {
131128
return errorMessage;
132129
}
133130

134131
var matchingTypes = candidates.Where(av => av._kind == referenceKind).ToArray();
135132
var matchingTypesString = matchingTypes.Length > 0
136-
? $"References that match type '{referenceKind}' have spans {string.Join(" ,", matchingTypes.Select(av => RangeToString(av.range)))}"
133+
? $"References that match type '{referenceKind}' have spans {string.Join(" ,", matchingTypes.Select(av => GetName(av.range)))}"
137134
: $"There are no references with type '{referenceKind}' either";
138135

139136
return $"{errorMessage} {matchingTypesString}";
140137
}
141-
142-
private static string RangeToString(Range range)
143-
=> $"({range.start.line}, {range.start.character}) - ({range.end.line}, {range.end.character})";
144-
145-
private static bool RangeEquals(Range r1, Range r2)
146-
=> r1.start.line == r2.start.line
147-
&& r1.start.character == r2.start.character
148-
&& r1.end.line == r2.end.line
149-
&& r1.end.character == r2.end.character;
150-
138+
151139
[CustomAssertion]
152-
private static string GetName() => CallerIdentifier.DetermineCallerIdentity() ?? "collection";
140+
private static string GetSubjectName() => CallerIdentifier.DetermineCallerIdentity() ?? "collection";
153141
}
154142
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Python Tools for Visual Studio
2+
// Copyright(c) Microsoft Corporation
3+
// All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
6+
// this file except in compliance with the License. You may obtain a copy of the
7+
// License at http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
10+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
11+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
12+
// MERCHANTABLITY OR NON-INFRINGEMENT.
13+
//
14+
// See the Apache Version 2.0 License for specific language governing
15+
// permissions and limitations under the License.
16+
17+
using System;
18+
using System.Collections.Generic;
19+
using System.Diagnostics.CodeAnalysis;
20+
using System.Linq;
21+
using FluentAssertions;
22+
using FluentAssertions.Collections;
23+
using FluentAssertions.Execution;
24+
using Microsoft.Python.LanguageServer;
25+
using static Microsoft.PythonTools.Analysis.FluentAssertions.AssertionsUtilities;
26+
27+
namespace Microsoft.PythonTools.Analysis.FluentAssertions {
28+
[ExcludeFromCodeCoverage]
29+
internal sealed class TextEditCollectionAssertions : SelfReferencingCollectionAssertions<TextEdit, TextEditCollectionAssertions> {
30+
public TextEditCollectionAssertions(IEnumerable<TextEdit> references) : base(references) {}
31+
32+
protected override string Identifier => nameof(TextEdit) + "Collection";
33+
34+
35+
[CustomAssertion]
36+
public AndConstraint<TextEditCollectionAssertions> OnlyHaveTextEdit(string expectedText, (int startLine, int startCharacter, int endLine, int endCharacter) expectedRange, string because = "", params object[] reasonArgs)
37+
=> OnlyHaveTextEdits(new []{(expectedText, expectedRange)}, because, reasonArgs);
38+
39+
[CustomAssertion]
40+
public AndConstraint<TextEditCollectionAssertions> OnlyHaveTextEdits(params (string expectedText, (int startLine, int startCharacter, int endLine, int endCharacter) expectedRange)[] textEdits)
41+
=> OnlyHaveTextEdits(textEdits, string.Empty);
42+
43+
[CustomAssertion]
44+
public AndConstraint<TextEditCollectionAssertions> OnlyHaveTextEdits(IEnumerable<(string expectedText, (int startLine, int startCharacter, int endLine, int endCharacter) expectedRange)> textEdits, string because = "", params object[] reasonArgs) {
45+
var expected = textEdits.ToArray();
46+
foreach (var (expectedText, (startLine, startCharacter, endLine, endCharacter)) in expected) {
47+
HaveTextEditAt(expectedText, (startLine, startCharacter, endLine, endCharacter), because, reasonArgs);
48+
}
49+
50+
var excess = Subject.Select(r => (r.newText, (r.range.start.line, r.range.start.character, r.range.end.line, r.range.end.character)))
51+
.Except(expected)
52+
.ToArray();
53+
54+
if (excess.Length > 0) {
55+
var excessString = string.Join(", ", excess.Select(((string text, (int, int, int, int) range) te) => $"({te.text}, {GetName(te.range)})"));
56+
var errorMessage = expected.Length > 1
57+
? $"Expected {GetSubjectName()} to have only {expected.Length} textEdits{{reason}}, but it also has textEdits: {excessString}."
58+
: expected.Length > 0
59+
? $"Expected {GetSubjectName()} to have only one reference{{reason}}, but it also has textEdits: {excessString}."
60+
: $"Expected {GetSubjectName()} to have no textEdits{{reason}}, but it has textEdits: {excessString}.";
61+
62+
Execute.Assertion.BecauseOf(because, reasonArgs).FailWith(errorMessage);
63+
}
64+
65+
return new AndConstraint<TextEditCollectionAssertions>(this);
66+
}
67+
68+
[CustomAssertion]
69+
public AndConstraint<TextEditCollectionAssertions> HaveTextEditAt(string expectedText, (int startLine, int startCharacter, int endLine, int endCharacter) expectedRange, string because = "", params object[] reasonArgs) {
70+
var range = new Range {
71+
start = new Position { line = expectedRange.startLine, character = expectedRange.startCharacter },
72+
end = new Position { line = expectedRange.endLine, character = expectedRange.endCharacter }
73+
};
74+
75+
var errorMessage = GetHaveTextEditErrorMessage(expectedText, range);
76+
Execute.Assertion.ForCondition(errorMessage == string.Empty)
77+
.BecauseOf(because, reasonArgs)
78+
.FailWith(errorMessage);
79+
80+
return new AndConstraint<TextEditCollectionAssertions>(this);
81+
}
82+
83+
[CustomAssertion]
84+
private string GetHaveTextEditErrorMessage(string expectedText, Range expectedRange) {
85+
var candidates = Subject.Where(av => string.Equals(av.newText, expectedText, StringComparison.Ordinal)).ToArray();
86+
if (candidates.Length == 0) {
87+
return $"Expected {GetSubjectName()} to have text edit with newText '{expectedText}'{{reason}}, but "
88+
+ (Subject.Any() ? "it is empty" : $"it has {GetQuotedNames(Subject.Select(te => te.newText))}");
89+
}
90+
91+
var candidatesWithRange = candidates.Where(c => RangeEquals(c.range, expectedRange)).ToArray();
92+
if (candidatesWithRange.Length > 1) {
93+
return $"Expected {GetSubjectName()} to have only one text edit with newText '{expectedText}' and range {GetName(expectedRange)}{{reason}}, but there are {candidatesWithRange.Length}";
94+
}
95+
96+
if (candidatesWithRange.Length == 0) {
97+
return $"Expected {GetSubjectName()} to have text edit with newText '{expectedText}' in range {GetName(expectedRange)} {{reason}}, but "
98+
+ (candidatesWithRange.Length == 1
99+
? $"it has range {GetName(candidatesWithRange[0].range)}"
100+
: $"they are in ranges {string.Join(", ", candidatesWithRange.Select(te => GetName(te.range)))}");
101+
}
102+
103+
return string.Empty;
104+
}
105+
106+
[CustomAssertion]
107+
private static string GetSubjectName() => CallerIdentifier.DetermineCallerIdentity() ?? "collection";
108+
}
109+
}

src/Analysis/Engine/Test/LanguageServerTests.cs

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,31 +1010,13 @@ public async Task OnTypeFormatting() {
10101010
// Extended tests for line formatting are in LineFormatterTests.
10111011
// These just verify that the language server formats and returns something correct.
10121012
var edits = await s.SendDocumentOnTypeFormatting(uri, new SourceLocation(2, 1), "\n");
1013-
edits.Should().OnlyContain(new TextEdit {
1014-
newText = "def foo():",
1015-
range = new Range {
1016-
start = new SourceLocation(1, 1),
1017-
end = new SourceLocation(1, 15)
1018-
}
1019-
});
1013+
edits.Should().OnlyHaveTextEdit("def foo():", (0, 0, 0, 14));
10201014

10211015
edits = await s.SendDocumentOnTypeFormatting(uri, new SourceLocation(3, 1), "\n");
1022-
edits.Should().OnlyContain(new TextEdit {
1023-
newText = "x = a + b",
1024-
range = new Range {
1025-
start = new SourceLocation(2, 5),
1026-
end = new SourceLocation(2, 14)
1027-
}
1028-
});
1016+
edits.Should().OnlyHaveTextEdit("x = a + b", (1, 4, 1, 13));
10291017

10301018
edits = await s.SendDocumentOnTypeFormatting(uri, new SourceLocation(4, 1), "\n");
1031-
edits.Should().OnlyContain(new TextEdit {
1032-
newText = "x += 1",
1033-
range = new Range {
1034-
start = new SourceLocation(3, 5),
1035-
end = new SourceLocation(3, 10)
1036-
}
1037-
});
1019+
edits.Should().OnlyHaveTextEdit("x += 1", (2, 4, 2, 9));
10381020
}
10391021
}
10401022

0 commit comments

Comments
 (0)