Merge lp:~adiroiban/pocket-lint/1082046-json-checker into lp:pocket-lint

Proposed by Adi Roiban
Status: Merged
Approved by: Curtis Hovey
Approved revision: 473
Merged at revision: 474
Proposed branch: lp:~adiroiban/pocket-lint/1082046-json-checker
Merge into: lp:pocket-lint
Diff against target: 364 lines (+245/-20)
4 files modified
pocketlint/formatcheck.py (+74/-18)
pocketlint/tests/test_json.py (+123/-0)
pocketlint/tests/test_restructuredtext.py (+2/-2)
pocketlint/tests/test_text.py (+46/-0)
To merge this branch: bzr merge lp:~adiroiban/pocket-lint/1082046-json-checker
Reviewer Review Type Date Requested Status
Curtis Hovey code Approve
Review via email: mp+137402@code.launchpad.net

Description of the change

Description
===========

The is no checker for JSON files.

JSON files are text files, but since no long lines breaker is supported in JSON, long lines are accepted for JSON.

All other plain text file checks should be applied.

Changes
=======

* A mimetype was forced for JSON since on WIndows it might not be automatically recognized.
* I have enabled the checker for ReStructuredText, since at the last merge I have only created the checker but not enabled it.
* I moved the checker_for_emtpy_last_line in the AnyTextCheckMixin together with the test.

I have a few drive by changes. Let me know if you want me to move them in another merge request.

I was not sure how to test basic plain text checks for JSON, so I just added simple test.

Let me know what needs to be changed for accepting this patch.

Thanks!

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. I will merge the branch.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'pocketlint/formatcheck.py'
--- pocketlint/formatcheck.py 2012-04-25 13:09:13 +0000
+++ pocketlint/formatcheck.py 2012-12-01 20:25:22 +0000
@@ -169,6 +169,7 @@
169 DOCTEST = object()169 DOCTEST = object()
170 CSS = object()170 CSS = object()
171 JAVASCRIPT = object()171 JAVASCRIPT = object()
172 JSON = object()
172 SH = object()173 SH = object()
173 XML = object()174 XML = object()
174 XSLT = object()175 XSLT = object()
@@ -182,6 +183,7 @@
182183
183 XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK)184 XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK)
184185
186 mimetypes.add_type('application/json', '.json')
185 mimetypes.add_type('application/x-zope-configuation', '.zcml')187 mimetypes.add_type('application/x-zope-configuation', '.zcml')
186 mimetypes.add_type('application/x-zope-page-template', '.pt')188 mimetypes.add_type('application/x-zope-page-template', '.pt')
187 mimetypes.add_type('text/x-python-doctest', '.doctest')189 mimetypes.add_type('text/x-python-doctest', '.doctest')
@@ -199,6 +201,7 @@
199 'text/x-log': LOG,201 'text/x-log': LOG,
200 'text/x-rst': RESTRUCTUREDTEXT,202 'text/x-rst': RESTRUCTUREDTEXT,
201 'application/javascript': JAVASCRIPT,203 'application/javascript': JAVASCRIPT,
204 'application/json': JSON,
202 'application/xml': XML,205 'application/xml': XML,
203 'application/x-sh': SH,206 'application/x-sh': SH,
204 'application/x-zope-configuation': ZCML,207 'application/x-zope-configuation': ZCML,
@@ -307,6 +310,10 @@
307 checker_class = XMLChecker310 checker_class = XMLChecker
308 elif self.language is Language.JAVASCRIPT:311 elif self.language is Language.JAVASCRIPT:
309 checker_class = JavascriptChecker312 checker_class = JavascriptChecker
313 elif self.language is Language.JSON:
314 checker_class = JSONChecker
315 elif self.language is Language.RESTRUCTUREDTEXT:
316 checker_class = ReStructuredTextChecker
310 elif self.language is Language.LOG:317 elif self.language is Language.LOG:
311 # Log files are not source, but they are often in source code318 # Log files are not source, but they are often in source code
312 # trees.319 # trees.
@@ -346,6 +353,19 @@
346 self.message(353 self.message(
347 line_no, 'Line contains a tab character.', icon='info')354 line_no, 'Line contains a tab character.', icon='info')
348355
356 def check_empty_last_line(self, total_lines):
357 """Chech the files ends with an emtpy line and not with double empty
358 line.
359
360 This will avoid merge conflicts.
361 """
362 if self.text[-1] != '\n' or self.text[-2:] == '\n\n':
363 self.message(
364 total_lines,
365 'File does not ends with an empty line.',
366 icon='info',
367 )
368
349369
350class AnyTextChecker(BaseChecker, AnyTextMixin):370class AnyTextChecker(BaseChecker, AnyTextMixin):
351 """Verify the text of the document."""371 """Verify the text of the document."""
@@ -611,7 +631,7 @@
611631
612632
613class JavascriptChecker(BaseChecker, AnyTextMixin):633class JavascriptChecker(BaseChecker, AnyTextMixin):
614 """Check python source code."""634 """Check JavaScript source code."""
615635
616 HERE = os.path.dirname(__file__)636 HERE = os.path.dirname(__file__)
617 FULLJSLINT = os.path.join(HERE, 'contrib/fulljslint.js')637 FULLJSLINT = os.path.join(HERE, 'contrib/fulljslint.js')
@@ -652,8 +672,56 @@
652 self.check_tab(line_no, line)672 self.check_tab(line_no, line)
653673
654674
675class JSONChecker(BaseChecker, AnyTextMixin):
676 """Check JSON files."""
677
678 def check(self):
679 """Check JSON file using basic text checks and custom checks."""
680 if not self.text:
681 return
682
683 # Line independent checks.
684 for line_no, line in enumerate(self.text.splitlines()):
685 line_no += 1
686 self.check_trailing_whitespace(line_no, line)
687 self.check_conflicts(line_no, line)
688 self.check_tab(line_no, line)
689 last_lineno = line_no
690
691 self.check_load()
692 self.check_empty_last_line(last_lineno)
693
694 def check_length(self, line_no, line):
695 """JSON files can have long lines."""
696 return
697
698 def check_load(self):
699 """Check that JSON can be deserialized/loaded."""
700 try:
701 import json
702 except ImportError:
703 try:
704 from simplejson import json
705 except ImportError:
706 raise AssertionError('JSON module could not be loaded.')
707
708 try:
709 json.loads(self.text)
710 except ValueError, error:
711 line_number = 0
712 message = error.message
713 match = re.search(r"(.*): line (\d+)", message)
714 if match:
715 try:
716 line_number = int(match.group(2))
717 except:
718 # If we can not find the line number,
719 # just fall back to default.
720 line_number = 0
721 self.message(line_number, message, icon='error')
722
655class ReStructuredTextChecker(BaseChecker, AnyTextMixin):723class ReStructuredTextChecker(BaseChecker, AnyTextMixin):
656 """Check reStructuredText ource code."""724 """Check reStructuredText source code."""
657725
658 # Taken from rst documentation.726 # Taken from rst documentation.
659 delimiter_characters = [727 delimiter_characters = [
@@ -667,8 +735,11 @@
667735
668 def check(self):736 def check(self):
669 """Check the syntax of the reStructuredText code."""737 """Check the syntax of the reStructuredText code."""
738 if not self.text:
739 return
740
670 self.check_lines()741 self.check_lines()
671 self.check_empty_last_line()742 self.check_empty_last_line(len(self.lines))
672743
673 def check_lines(self):744 def check_lines(self):
674 """Call each line checker for each line in text."""745 """Call each line checker for each line in text."""
@@ -878,21 +949,6 @@
878949
879 return True950 return True
880951
881 def check_empty_last_line(self):
882 """Chech the files ends with an emtpy line and not with double empty
883 line.
884
885 This will avoid merge conflicts.
886 """
887 if len(self.lines) < 2:
888 return
889 if self.text[-1] != '\n' or self.text[-2:] == '\n\n':
890 self.message(
891 len(self.lines),
892 'File does not ends with an empty line.',
893 icon='info',
894 )
895
896952
897def get_option_parser():953def get_option_parser():
898 """Return the option parser for this program."""954 """Return the option parser for this program."""
899955
=== added file 'pocketlint/tests/test_json.py'
--- pocketlint/tests/test_json.py 1970-01-01 00:00:00 +0000
+++ pocketlint/tests/test_json.py 2012-12-01 20:25:22 +0000
@@ -0,0 +1,123 @@
1"""
2Tests for JSON files.
3"""
4
5from pocketlint.formatcheck import JSONChecker
6from pocketlint.tests import CheckerTestCase
7
8
9class TestJSON(CheckerTestCase):
10 """Verify JSON validation."""
11
12 def test_empty_file(self):
13 """
14 No errors are be reported for empty files.
15 """
16 self.reporter.call_count = 0
17 content = ''
18 checker = JSONChecker('bogus', content, self.reporter)
19
20 checker.check()
21
22 self.assertEqual([], self.reporter.messages)
23 self.assertEqual(0, self.reporter.call_count)
24
25 def test_long_line(self):
26 """
27 No errors are be reported for long lines.
28 """
29 self.reporter.call_count = 0
30 content = '{"1": "' + 'a' * 100 + '"}\n'
31 checker = JSONChecker('bogus', content, self.reporter)
32
33 checker.check()
34
35 self.assertEqual([], self.reporter.messages)
36 self.assertEqual(0, self.reporter.call_count)
37
38 def test_trailing_spaces(self):
39 """
40 Short test to check that trailing spaces are catched.
41 """
42 self.reporter.call_count = 0
43 content = '{} \n'
44 checker = JSONChecker('bogus', content, self.reporter)
45
46 checker.check()
47
48 self.assertEqual(
49 [(1, 'Line has trailing whitespace.')],
50 self.reporter.messages)
51 self.assertEqual(1, self.reporter.call_count)
52
53 def test_conflict_markups(self):
54 """
55 Short test to check that merge conflicts are catched.
56 """
57 self.reporter.call_count = 0
58 content = '{}\n>>>>>>>\n'
59 checker = JSONChecker('bogus', content, self.reporter)
60
61 checker.check()
62
63 self.assertEqual(
64 (2, 'File has conflicts.'),
65 self.reporter.messages[0])
66
67 def test_tabs(self):
68 """
69 Short test to check that tab characters are catched.
70 """
71 self.reporter.call_count = 0
72 content = '\t{}\n'
73 checker = JSONChecker('bogus', content, self.reporter)
74
75 checker.check()
76
77 self.assertEqual(
78 [(1, 'Line contains a tab character.')],
79 self.reporter.messages)
80 self.assertEqual(1, self.reporter.call_count)
81
82 def test_unable_to_compile(self):
83 """
84 Line number '0' is reported if JSON can not be compiled at all.
85 """
86 content = '\n\nno-json-object\n'
87 checker = JSONChecker('bogus', content, self.reporter)
88
89 checker.check()
90
91 self.assertEqual(
92 [(0, 'No JSON object could be decoded')],
93 self.reporter.messages)
94 self.assertEqual(1, self.reporter.call_count)
95
96 def test_compile_error_with_line(self):
97 """
98 When the JSON module identifies an error at a line, it will be
99 set by pocketlint.
100 """
101 content = '{\n1: "something"}\n'
102 checker = JSONChecker('bogus', content, self.reporter)
103 checker.check()
104
105 self.assertEqual(
106 [(2, 'Expecting property name: line 2 column 1 (char 2)')],
107 self.reporter.messages)
108 self.assertEqual(1, self.reporter.call_count)
109
110 def test_compile_error_on_multiple_line(self):
111 """
112 When the JSON error is reported on multiple lines, only the first
113 line is reported as the line number.
114 """
115 content = '{}\n}\n}\n'
116 checker = JSONChecker('bogus', content, self.reporter)
117 checker.check()
118
119 self.assertEqual(
120 [(2,
121 'Extra data: line 2 column 1 - line 4 column 1 (char 3 - 7)')],
122 self.reporter.messages)
123 self.assertEqual(1, self.reporter.call_count)
0124
=== modified file 'pocketlint/tests/test_restructuredtext.py'
--- pocketlint/tests/test_restructuredtext.py 2012-04-21 17:02:14 +0000
+++ pocketlint/tests/test_restructuredtext.py 2012-12-01 20:25:22 +0000
@@ -137,7 +137,7 @@
137 'the second and last line witout newline'137 'the second and last line witout newline'
138 )138 )
139 checker = ReStructuredTextChecker('bogus', content, self.reporter)139 checker = ReStructuredTextChecker('bogus', content, self.reporter)
140 checker.check_empty_last_line()140 checker.check_empty_last_line(2)
141 expected = [(141 expected = [(
142 2, 'File does not ends with an empty line.')]142 2, 'File does not ends with an empty line.')]
143 self.assertEqual(expected, self.reporter.messages)143 self.assertEqual(expected, self.reporter.messages)
@@ -151,7 +151,7 @@
151 '\n'151 '\n'
152 )152 )
153 checker = ReStructuredTextChecker('bogus', content, self.reporter)153 checker = ReStructuredTextChecker('bogus', content, self.reporter)
154 checker.check_empty_last_line()154 checker.check_empty_last_line(3)
155 expected = [(155 expected = [(
156 3, 'File does not ends with an empty line.')]156 3, 'File does not ends with an empty line.')]
157 self.assertEqual(expected, self.reporter.messages)157 self.assertEqual(expected, self.reporter.messages)
158158
=== modified file 'pocketlint/tests/test_text.py'
--- pocketlint/tests/test_text.py 2012-01-29 21:24:20 +0000
+++ pocketlint/tests/test_text.py 2012-12-01 20:25:22 +0000
@@ -75,3 +75,49 @@
75 self.assertEqual(75 self.assertEqual(
76 [(1, 'Line exceeds 49 characters.')],76 [(1, 'Line exceeds 49 characters.')],
77 self.reporter.messages)77 self.reporter.messages)
78
79 def test_no_empty_last_line(self):
80 """
81 An error is reported if file does not end with a new lines.
82 """
83 content = (
84 'Some first line\n'
85 'the second and last line witout newline'
86 )
87 checker = AnyTextChecker('bogus', content, self.reporter)
88 checker.check_empty_last_line(2)
89 expected = [(
90 2, 'File does not ends with an empty line.')]
91 self.assertEqual(expected, self.reporter.messages)
92 self.assertEqual(1, self.reporter.call_count)
93
94 def test_multiple_empty_last_lines(self):
95 """
96 An error is reported if file ends with multiple new lines.
97 """
98 content = (
99 'Some first line\n'
100 'the second and last\n'
101 '\n'
102 )
103 checker = AnyTextChecker('bogus', content, self.reporter)
104 checker.check_empty_last_line(3)
105 expected = [(
106 3, 'File does not ends with an empty line.')]
107 self.assertEqual(expected, self.reporter.messages)
108 self.assertEqual(1, self.reporter.call_count)
109
110 def test_single_last_line_no_newline(self):
111 """
112 An error is reported if file contains a single line and does not end
113 with a new lines.
114 """
115 content = (
116 'the second and last line witout newline'
117 )
118 checker = AnyTextChecker('bogus', content, self.reporter)
119 checker.check_empty_last_line(2)
120 expected = [(
121 2, 'File does not ends with an empty line.')]
122 self.assertEqual(expected, self.reporter.messages)
123 self.assertEqual(1, self.reporter.call_count)

Subscribers

People subscribed via source and target branches

to all changes: