-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
""" | ||
The :mod:`Diagnostic` module concerns itself with processing | ||
and presentation of diagnostic messages. | ||
""" | ||
|
||
class Diagnostic: | ||
""" | ||
A diagnostic message highlighting one or more locations | ||
in a single source buffer. | ||
:ivar level: (one of ``LEVELS``) severity level | ||
:ivar reason: (format string) diagnostic message | ||
:ivar arguments: (dictionary) substitutions for ``reason`` | ||
:ivar location: (:class:`pyparser.source.Range`) most specific | ||
location of the problem | ||
:ivar highlights: (list of :class:`pyparser.source.Range`) | ||
secondary locations related to the problem that are | ||
likely to be on the same line | ||
:ivar notes: (list of :class:`Diagnostic`) | ||
secondary diagnostics highlighting relevant source | ||
locations that are unlikely to be on the same line | ||
""" | ||
|
||
LEVELS = ['note', 'warning', 'error', 'fatal'] | ||
""" | ||
Available diagnostic levels: | ||
* ``fatal`` indicates an unrecoverable error. | ||
* ``error`` indicates an error that leaves a possibility of | ||
processing more code, e.g. a recoverable parsing error. | ||
* ``warning`` indicates a potential problem. | ||
* ``note`` level diagnostics do not appear by itself, | ||
but are attached to other diagnostics to refer to | ||
and describe secondary source locations. | ||
""" | ||
|
||
def __init__(self, level, reason, arguments, location, | ||
highlights=[], notes=[]): | ||
if level not in self.LEVELS: | ||
raise ValueError, "level must be one of Diagnostic.LEVELS" | ||
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
if len(set(map(lambda x: x.source_buffer, | ||
[location] + highlights))) > 1: | ||
raise ValueError, "location and highlights must refer to the same source buffer" | ||
|
||
self.level, self.reason, self.arguments = \ | ||
level, reason, arguments | ||
self.location, self.highlights, self.notes = \ | ||
location, highlights, notes | ||
|
||
def message(self): | ||
""" | ||
Returns the formatted message. | ||
""" | ||
return self.reason.format(**self.arguments) | ||
|
||
def render(self): | ||
""" | ||
Returns the human-readable location of the diagnostic in the source, | ||
the formatted message, the source line corresponding | ||
to ``location`` and a line emphasizing the problematic | ||
locations in the source line using ASCII art, as a list of lines. | ||
""" | ||
source_line = self.location.source_line().rstrip(u"\n") | ||
highlight_line = bytearray(' ') * len(source_line) | ||
|
||
for hilight in self.highlights: | ||
lft, rgt = hilight.column_range() | ||
highlight_line[lft:rgt] = bytearray('~') * hilight.size() | ||
|
||
lft, rgt = self.location.column_range() | ||
highlight_line[lft:rgt] = bytearray('^') * self.location.size() | ||
|
||
return [ | ||
"%s: %s: %s" % (str(self.location), self.level, self.message()), | ||
source_line, | ||
unicode(highlight_line) | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import unittest | ||
import pyparser.source as source | ||
import pyparser.diagnostic as diagnostic | ||
|
||
class DiagnosticTestCase(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.buffer = source.Buffer(u'x + (1 + "a")\n') | ||
|
||
def test_message(self): | ||
diag = diagnostic.Diagnostic( | ||
'error', u"{x} doesn't work", {'x': 'everything'}, | ||
source.Range(self.buffer, 0, 0)) | ||
self.assertEqual(u"everything doesn't work", diag.message()) | ||
|
||
def test_render(self): | ||
diag = diagnostic.Diagnostic( | ||
'error', u"cannot add {lft} and {rgt}", | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
{'lft': u'integer', 'rgt': u'string'}, | ||
source.Range(self.buffer, 7, 8), | ||
[source.Range(self.buffer, 5, 6), | ||
source.Range(self.buffer, 9, 12)]) | ||
self.assertEqual( | ||
[u'<input>:1:8: error: cannot add integer and string', | ||
u'x + (1 + "a")', | ||
u' ~ ^ ~~~ '], | ||
diag.render()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,16 +4,16 @@ | |
class BufferTestCase(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.buffer = source.Buffer("line one\nline two\n\nline four") | ||
self.buffer = source.Buffer(u"line one\nline two\n\nline four") | ||
|
||
def test_repr(self): | ||
self.assertEqual(r'Buffer("<input>")', repr(self.buffer)) | ||
self.assertEqual(ur'Buffer("<input>")', repr(self.buffer)) | ||
|
||
def test_source_line(self): | ||
self.assertEqual("line one\n", self.buffer.source_line(1)) | ||
self.assertEqual("line two\n", self.buffer.source_line(2)) | ||
self.assertEqual("\n", self.buffer.source_line(3)) | ||
self.assertEqual("line four", self.buffer.source_line(4)) | ||
self.assertEqual(u"line one\n", self.buffer.source_line(1)) | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
whitequark
Author
Contributor
|
||
self.assertEqual(u"line two\n", self.buffer.source_line(2)) | ||
self.assertEqual(u"\n", self.buffer.source_line(3)) | ||
self.assertEqual(u"line four", self.buffer.source_line(4)) | ||
self.assertRaises(IndexError, lambda: self.buffer.source_line(0)) | ||
|
||
def test_decompose_position(self): | ||
|
@@ -26,13 +26,13 @@ def test_decompose_position(self): | |
class RangeTestCase(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.buffer = source.Buffer("line one\nline two\n\nline four") | ||
self.buffer = source.Buffer(u"line one\nline two\n\nline four") | ||
|
||
def range(self, lft, rgt): | ||
return source.Range(self.buffer, lft, rgt) | ||
|
||
def test_repr(self): | ||
self.assertEqual(r'Range("<input>", 0, 2)', | ||
self.assertEqual(ur'Range("<input>", 0, 2)', | ||
repr(self.range(0, 2))) | ||
|
||
def test_begin(self): | ||
|
@@ -74,13 +74,13 @@ def test_join(self): | |
source.Range(source.Buffer(""), 0, 0))) | ||
|
||
def test_source(self): | ||
self.assertEqual("one", self.range(5, 8).source()) | ||
self.assertEqual(u"one", self.range(5, 8).source()) | ||
|
||
def test_source_line(self): | ||
self.assertEqual("line two\n", self.range(9, 9).source_line()) | ||
self.assertEqual(u"line two\n", self.range(9, 9).source_line()) | ||
|
||
def test___str__(self): | ||
self.assertEqual("<input>:2:1", str(self.range(9, 9))) | ||
self.assertEqual(u"<input>:2:1", str(self.range(9, 9))) | ||
|
||
def test___ne__(self): | ||
self.assertTrue(self.range(0,0) != self.range(0,1)) | ||
|
raise ValueError("level must be one of Diagnostic.LEVELS")