Browse Source

[Utils] Add lots of error checks to CompositeFormatStringParser.

newNRvisualizers
Simon Lindgren 13 years ago
parent
commit
1b96e9b091
  1. 108
      ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs
  2. 206
      ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs
  3. 7
      ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/IFormatStringError.cs

108
ICSharpCode.NRefactory.Tests/Utils/CompositeFormatStringParser/CompositeFormatStringParserTests.cs

@ -52,6 +52,21 @@ namespace ICSharpCode.NRefactory.Utils @@ -52,6 +52,21 @@ namespace ICSharpCode.NRefactory.Utils
return actualFormatSegments;
}
static IList<IFormatStringError> SegmentTest(int count, IFormatStringSegment segment)
{
var errors = segment.Errors.ToList();
Assert.AreEqual(count, errors.Count, "Too many or too few errors.");
return errors;
}
static void ErrorTest(IFormatStringError error, string originalText, string replacementText, int startLocation, int endLocation)
{
Assert.AreEqual(originalText, error.OriginalText, "OriginalText is incorrect.");
Assert.AreEqual(replacementText, error.SuggestedReplacementText, "SuggestedReplacementText is incorrect.");
Assert.AreEqual(startLocation, error.StartLocation, "StartLocation is incorrect.");
Assert.AreEqual(endLocation, error.EndLocation, "EndLocation is incorrect.");
}
[Test]
public void Index()
{
@ -135,18 +150,99 @@ namespace ICSharpCode.NRefactory.Utils @@ -135,18 +150,99 @@ namespace ICSharpCode.NRefactory.Utils
{
ParseTest("A weird string: {0:{{}}}",
new TextSegment("A weird string: "),
new FormatItem(0, null, "{}") { StartLocation = 16, EndLocation = 24});
new FormatItem(0, null, "{}") { StartLocation = 16, EndLocation = 24 });
}
[Test]
public void EmptySubFormatString()
{
ParseTest("{0:}", new FormatItem(0, null, "") { StartLocation = 0, EndLocation = 4 });
}
[Test]
public void EndsAfterOpenBrace()
{
var segments = ParseTest("{", new TextSegment("{"));
var segment = segments [0];
var errors = segment.Errors.ToList();
Assert.AreEqual(1, errors.Count, "Too many or too few errors.");
var error = errors [0];
Assert.AreEqual("{{", error.SuggestedReplacementText);
var errors = SegmentTest(1, segments.First());
ErrorTest(errors[0], "{", "{{", 0, 1);
}
[Test]
public void UnescapedLoneEndingBrace()
{
var segments = ParseTest("Some text {", new TextSegment("Some text {"));
var errors = SegmentTest(1, segments.First());
ErrorTest(errors[0], "{", "{{", 10, 11);
}
[Test]
public void EndAfterIndex()
{
var segments = ParseTest("Some text {0",
new TextSegment("Some text "),
new FormatItem(0) { StartLocation = 10, EndLocation = 12 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], "", "}", 12, 12);
}
[Test]
public void EndAfterComma()
{
var segments = ParseTest("Some text {0,",
new TextSegment("Some text "),
new FormatItem(0) { StartLocation = 10, EndLocation = 13 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], ",", "}", 12, 13);
}
[Test]
public void EndAfterCommaAndSpaces()
{
var segments = ParseTest("Some text {0, ",
new TextSegment("Some text "),
new FormatItem(0) { StartLocation = 10, EndLocation = 16 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], ", ", "}", 12, 16);
}
[Test]
public void EndAfterAlignment()
{
var segments = ParseTest("Some text {0, -34",
new TextSegment("Some text "),
new FormatItem(0, -34) { StartLocation = 10, EndLocation = 17 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], "", "}", 17, 17);
}
[Test]
public void EndAfterColon()
{
var segments = ParseTest("Some text {0:",
new TextSegment("Some text "),
new FormatItem(0, null, "") { StartLocation = 10, EndLocation = 13 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], "", "}", 13, 13);
}
[Test]
public void EndAfterSubFormatString()
{
var segments = ParseTest("Some text {0: asdf",
new TextSegment("Some text "),
new FormatItem(0, null, " asdf") { StartLocation = 10, EndLocation = 18 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], "", "}", 18, 18);
}
[Test]
public void MissingIndex()
{
var segments = ParseTest("Some text {}",
new TextSegment("Some text "),
new FormatItem(0) { StartLocation = 10, EndLocation = 12 });
var errors = SegmentTest(1, segments.Skip(1).First());
ErrorTest(errors[0], "", "0", 11, 11);
}
}
}

206
ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/CompositeFormatStringParser.cs

@ -37,6 +37,12 @@ namespace ICSharpCode.NRefactory.Utils @@ -37,6 +37,12 @@ namespace ICSharpCode.NRefactory.Utils
/// </remarks>
public class CompositeFormatStringParser
{
public CompositeFormatStringParser ()
{
errors = new List<IFormatStringError> ();
}
/// <summary>
/// Parse the specified format string.
/// </summary>
@ -45,6 +51,9 @@ namespace ICSharpCode.NRefactory.Utils @@ -45,6 +51,9 @@ namespace ICSharpCode.NRefactory.Utils
/// </param>
public IEnumerable<IFormatStringSegment> Parse (string format)
{
if (format == null)
throw new ArgumentNullException ("format");
// Format string syntax: http://msdn.microsoft.com/en-us/library/txafckwd.aspx
int start = 0;
var length = format.Length;
@ -84,45 +93,26 @@ namespace ICSharpCode.NRefactory.Utils @@ -84,45 +93,26 @@ namespace ICSharpCode.NRefactory.Utils
// Index
++i;
index = int.Parse (GetUntil (format, ",:}", ref i));
index = ParseIndex(format, ref i);
CheckForMissingEndBrace (format, i, length);
// Alignment
if (format [i] == ',') {
++i;
while (i < length && char.IsWhiteSpace(format [i]))
++i;
if (format [i] == '-') {
++i;
alignment = -int.Parse (GetUntil (format, ":}", ref i));
} else {
alignment = int.Parse (GetUntil (format, ":}", ref i));
}
}
alignment = ParseAlignment(format, ref i, length);
CheckForMissingEndBrace (format, i, length);
// Format string
if (format [i] == ':') {
++i;
int begin = i;
while (i < length) {
char c2 = format [i];
if (c2 != '}') {
++i;
continue;
}
if (i + 1 < length && format [i + 1] == '}') {
// Step past escape sequence
i += 2;
continue;
} else {
// This is the end of the FormatItem
break;
}
}
argumentFormat = TextSegment.UnEscape (format.Substring (begin, i - begin));
}
argumentFormat = ParseSubFormatString(format, ref i, length);
CheckForMissingEndBrace (format, i, length);
yield return new FormatItem (index, alignment, argumentFormat) { StartLocation = start, EndLocation = i + 1 };
// i may actually point outside of format; if that happens, we want the last position
var endLocation = Math.Min (length, i + 1);
var errors = GetErrors ();
yield return new FormatItem (index, alignment, argumentFormat) {
StartLocation = start,
EndLocation = endLocation,
Errors = errors
};
ClearErrors ();
// The next potential text segment starts after this format item
start = i + 1;
@ -134,15 +124,161 @@ namespace ICSharpCode.NRefactory.Utils @@ -134,15 +124,161 @@ namespace ICSharpCode.NRefactory.Utils
}
}
int ParseIndex (string format, ref int i)
{
int? maybeIndex = GetNumber (format, ref i);
if (maybeIndex.HasValue) {
return maybeIndex.Value;
}
AddError (new DefaultFormatStringError {
StartLocation = i,
EndLocation = i,
Message = "Missing index",
OriginalText = "",
SuggestedReplacementText = "0"
});
return 0;
}
int? ParseAlignment(string format, ref int i, int length)
{
if (i < length && format [i] == ',') {
int alignmentBegin = i;
++i;
while (i < length && char.IsWhiteSpace(format [i]))
++i;
if (i == length) {
var originalText = format.Substring (alignmentBegin);
var message = string.Format ("Unexpected end of string: '{0}'", originalText);
AddMissingEndBraceError(alignmentBegin, i, message, originalText);
} else if (format [i] == '-') {
++i;
return -int.Parse (GetUntil (format, ":}", ref i));
} else {
return int.Parse (GetUntil (format, ":}", ref i));
}
}
return null;
}
string ParseSubFormatString(string format, ref int i, int length)
{
if (i < length && format [i] == ':') {
++i;
int begin = i;
while (i < length) {
char c = format [i];
if (c != '}') {
++i;
continue;
}
if (i + 1 < length && format [i + 1] == '}') {
// Step past escape sequence
i += 2;
continue;
} else {
// This is the end of the FormatItem
break;
}
}
var escaped = format.Substring (begin, i - begin);
return TextSegment.UnEscape (escaped);
}
return null;
}
string GetFormatItemText (string src, int index, out int endIndex)
{
int length = src.Length;
int begin = index;
while (index < length) {
var c = src [index];
++index;
if (c != '}') {
continue;
} else if (index + 1 < length && src [index + 1] == '}') {
// Step past escape sequence
++index;
continue;
} else {
// This is the end of the FormatItem
break;
}
}
endIndex = index < length ? index : length;
return src.Substring (begin, endIndex - begin);
}
void CheckForMissingEndBrace (string format, int i, int length)
{
if (i == length && format [length - 1] != '}') {
AddMissingEndBraceError(i, i, "Missing '}'", "");
return;
}
return;
}
string GetUntil (string format, string delimiters, ref int index)
{
int start = index;
while (index < format.Length && !delimiters.Contains(format[index].ToString()))
++index;
return format.Substring (start, index - start);
}
int? GetNumber (string format, ref int index)
{
int sum = 0;
int i = index;
bool positive = format [i] != '-';
if (!positive)
++i;
int numberStartIndex = i;
while (i < format.Length && format[i] >= '0' && format[i] <= '9') {
sum = 10 * sum + format [i] - '0';
++i;
}
if (i == numberStartIndex)
return null;
index = i;
return positive ? sum : -sum;
}
IList<IFormatStringError> errors;
bool hasMissingEndBrace = false;
void AddError (IFormatStringError error)
{
errors.Add (error);
}
void AddMissingEndBraceError(int start, int end, string message, string originalText)
{
if (hasMissingEndBrace)
return;
AddError (new DefaultFormatStringError {
StartLocation = start,
EndLocation = end,
Message = message,
OriginalText = originalText,
SuggestedReplacementText = "}"
});
hasMissingEndBrace = true;
}
IList<IFormatStringError> GetErrors ()
{
return errors;
}
void ClearErrors ()
{
hasMissingEndBrace = false;
errors = new List<IFormatStringError> ();
}
}
}

7
ICSharpCode.NRefactory/Utils/CompositeFormatStringParser/IFormatStringError.cs

@ -41,6 +41,13 @@ namespace ICSharpCode.NRefactory.Utils @@ -41,6 +41,13 @@ namespace ICSharpCode.NRefactory.Utils
class DefaultFormatStringError : IFormatStringError
{
public DefaultFormatStringError()
{
Message = "";
OriginalText = "";
SuggestedReplacementText = "";
}
#region IFormatStringError implementation
public int StartLocation { get; set; }

Loading…
Cancel
Save