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
1=== modified file 'pocketlint/formatcheck.py'
2--- pocketlint/formatcheck.py 2012-04-25 13:09:13 +0000
3+++ pocketlint/formatcheck.py 2012-12-01 20:25:22 +0000
4@@ -169,6 +169,7 @@
5 DOCTEST = object()
6 CSS = object()
7 JAVASCRIPT = object()
8+ JSON = object()
9 SH = object()
10 XML = object()
11 XSLT = object()
12@@ -182,6 +183,7 @@
13
14 XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK)
15
16+ mimetypes.add_type('application/json', '.json')
17 mimetypes.add_type('application/x-zope-configuation', '.zcml')
18 mimetypes.add_type('application/x-zope-page-template', '.pt')
19 mimetypes.add_type('text/x-python-doctest', '.doctest')
20@@ -199,6 +201,7 @@
21 'text/x-log': LOG,
22 'text/x-rst': RESTRUCTUREDTEXT,
23 'application/javascript': JAVASCRIPT,
24+ 'application/json': JSON,
25 'application/xml': XML,
26 'application/x-sh': SH,
27 'application/x-zope-configuation': ZCML,
28@@ -307,6 +310,10 @@
29 checker_class = XMLChecker
30 elif self.language is Language.JAVASCRIPT:
31 checker_class = JavascriptChecker
32+ elif self.language is Language.JSON:
33+ checker_class = JSONChecker
34+ elif self.language is Language.RESTRUCTUREDTEXT:
35+ checker_class = ReStructuredTextChecker
36 elif self.language is Language.LOG:
37 # Log files are not source, but they are often in source code
38 # trees.
39@@ -346,6 +353,19 @@
40 self.message(
41 line_no, 'Line contains a tab character.', icon='info')
42
43+ def check_empty_last_line(self, total_lines):
44+ """Chech the files ends with an emtpy line and not with double empty
45+ line.
46+
47+ This will avoid merge conflicts.
48+ """
49+ if self.text[-1] != '\n' or self.text[-2:] == '\n\n':
50+ self.message(
51+ total_lines,
52+ 'File does not ends with an empty line.',
53+ icon='info',
54+ )
55+
56
57 class AnyTextChecker(BaseChecker, AnyTextMixin):
58 """Verify the text of the document."""
59@@ -611,7 +631,7 @@
60
61
62 class JavascriptChecker(BaseChecker, AnyTextMixin):
63- """Check python source code."""
64+ """Check JavaScript source code."""
65
66 HERE = os.path.dirname(__file__)
67 FULLJSLINT = os.path.join(HERE, 'contrib/fulljslint.js')
68@@ -652,8 +672,56 @@
69 self.check_tab(line_no, line)
70
71
72+class JSONChecker(BaseChecker, AnyTextMixin):
73+ """Check JSON files."""
74+
75+ def check(self):
76+ """Check JSON file using basic text checks and custom checks."""
77+ if not self.text:
78+ return
79+
80+ # Line independent checks.
81+ for line_no, line in enumerate(self.text.splitlines()):
82+ line_no += 1
83+ self.check_trailing_whitespace(line_no, line)
84+ self.check_conflicts(line_no, line)
85+ self.check_tab(line_no, line)
86+ last_lineno = line_no
87+
88+ self.check_load()
89+ self.check_empty_last_line(last_lineno)
90+
91+ def check_length(self, line_no, line):
92+ """JSON files can have long lines."""
93+ return
94+
95+ def check_load(self):
96+ """Check that JSON can be deserialized/loaded."""
97+ try:
98+ import json
99+ except ImportError:
100+ try:
101+ from simplejson import json
102+ except ImportError:
103+ raise AssertionError('JSON module could not be loaded.')
104+
105+ try:
106+ json.loads(self.text)
107+ except ValueError, error:
108+ line_number = 0
109+ message = error.message
110+ match = re.search(r"(.*): line (\d+)", message)
111+ if match:
112+ try:
113+ line_number = int(match.group(2))
114+ except:
115+ # If we can not find the line number,
116+ # just fall back to default.
117+ line_number = 0
118+ self.message(line_number, message, icon='error')
119+
120 class ReStructuredTextChecker(BaseChecker, AnyTextMixin):
121- """Check reStructuredText ource code."""
122+ """Check reStructuredText source code."""
123
124 # Taken from rst documentation.
125 delimiter_characters = [
126@@ -667,8 +735,11 @@
127
128 def check(self):
129 """Check the syntax of the reStructuredText code."""
130+ if not self.text:
131+ return
132+
133 self.check_lines()
134- self.check_empty_last_line()
135+ self.check_empty_last_line(len(self.lines))
136
137 def check_lines(self):
138 """Call each line checker for each line in text."""
139@@ -878,21 +949,6 @@
140
141 return True
142
143- def check_empty_last_line(self):
144- """Chech the files ends with an emtpy line and not with double empty
145- line.
146-
147- This will avoid merge conflicts.
148- """
149- if len(self.lines) < 2:
150- return
151- if self.text[-1] != '\n' or self.text[-2:] == '\n\n':
152- self.message(
153- len(self.lines),
154- 'File does not ends with an empty line.',
155- icon='info',
156- )
157-
158
159 def get_option_parser():
160 """Return the option parser for this program."""
161
162=== added file 'pocketlint/tests/test_json.py'
163--- pocketlint/tests/test_json.py 1970-01-01 00:00:00 +0000
164+++ pocketlint/tests/test_json.py 2012-12-01 20:25:22 +0000
165@@ -0,0 +1,123 @@
166+"""
167+Tests for JSON files.
168+"""
169+
170+from pocketlint.formatcheck import JSONChecker
171+from pocketlint.tests import CheckerTestCase
172+
173+
174+class TestJSON(CheckerTestCase):
175+ """Verify JSON validation."""
176+
177+ def test_empty_file(self):
178+ """
179+ No errors are be reported for empty files.
180+ """
181+ self.reporter.call_count = 0
182+ content = ''
183+ checker = JSONChecker('bogus', content, self.reporter)
184+
185+ checker.check()
186+
187+ self.assertEqual([], self.reporter.messages)
188+ self.assertEqual(0, self.reporter.call_count)
189+
190+ def test_long_line(self):
191+ """
192+ No errors are be reported for long lines.
193+ """
194+ self.reporter.call_count = 0
195+ content = '{"1": "' + 'a' * 100 + '"}\n'
196+ checker = JSONChecker('bogus', content, self.reporter)
197+
198+ checker.check()
199+
200+ self.assertEqual([], self.reporter.messages)
201+ self.assertEqual(0, self.reporter.call_count)
202+
203+ def test_trailing_spaces(self):
204+ """
205+ Short test to check that trailing spaces are catched.
206+ """
207+ self.reporter.call_count = 0
208+ content = '{} \n'
209+ checker = JSONChecker('bogus', content, self.reporter)
210+
211+ checker.check()
212+
213+ self.assertEqual(
214+ [(1, 'Line has trailing whitespace.')],
215+ self.reporter.messages)
216+ self.assertEqual(1, self.reporter.call_count)
217+
218+ def test_conflict_markups(self):
219+ """
220+ Short test to check that merge conflicts are catched.
221+ """
222+ self.reporter.call_count = 0
223+ content = '{}\n>>>>>>>\n'
224+ checker = JSONChecker('bogus', content, self.reporter)
225+
226+ checker.check()
227+
228+ self.assertEqual(
229+ (2, 'File has conflicts.'),
230+ self.reporter.messages[0])
231+
232+ def test_tabs(self):
233+ """
234+ Short test to check that tab characters are catched.
235+ """
236+ self.reporter.call_count = 0
237+ content = '\t{}\n'
238+ checker = JSONChecker('bogus', content, self.reporter)
239+
240+ checker.check()
241+
242+ self.assertEqual(
243+ [(1, 'Line contains a tab character.')],
244+ self.reporter.messages)
245+ self.assertEqual(1, self.reporter.call_count)
246+
247+ def test_unable_to_compile(self):
248+ """
249+ Line number '0' is reported if JSON can not be compiled at all.
250+ """
251+ content = '\n\nno-json-object\n'
252+ checker = JSONChecker('bogus', content, self.reporter)
253+
254+ checker.check()
255+
256+ self.assertEqual(
257+ [(0, 'No JSON object could be decoded')],
258+ self.reporter.messages)
259+ self.assertEqual(1, self.reporter.call_count)
260+
261+ def test_compile_error_with_line(self):
262+ """
263+ When the JSON module identifies an error at a line, it will be
264+ set by pocketlint.
265+ """
266+ content = '{\n1: "something"}\n'
267+ checker = JSONChecker('bogus', content, self.reporter)
268+ checker.check()
269+
270+ self.assertEqual(
271+ [(2, 'Expecting property name: line 2 column 1 (char 2)')],
272+ self.reporter.messages)
273+ self.assertEqual(1, self.reporter.call_count)
274+
275+ def test_compile_error_on_multiple_line(self):
276+ """
277+ When the JSON error is reported on multiple lines, only the first
278+ line is reported as the line number.
279+ """
280+ content = '{}\n}\n}\n'
281+ checker = JSONChecker('bogus', content, self.reporter)
282+ checker.check()
283+
284+ self.assertEqual(
285+ [(2,
286+ 'Extra data: line 2 column 1 - line 4 column 1 (char 3 - 7)')],
287+ self.reporter.messages)
288+ self.assertEqual(1, self.reporter.call_count)
289
290=== modified file 'pocketlint/tests/test_restructuredtext.py'
291--- pocketlint/tests/test_restructuredtext.py 2012-04-21 17:02:14 +0000
292+++ pocketlint/tests/test_restructuredtext.py 2012-12-01 20:25:22 +0000
293@@ -137,7 +137,7 @@
294 'the second and last line witout newline'
295 )
296 checker = ReStructuredTextChecker('bogus', content, self.reporter)
297- checker.check_empty_last_line()
298+ checker.check_empty_last_line(2)
299 expected = [(
300 2, 'File does not ends with an empty line.')]
301 self.assertEqual(expected, self.reporter.messages)
302@@ -151,7 +151,7 @@
303 '\n'
304 )
305 checker = ReStructuredTextChecker('bogus', content, self.reporter)
306- checker.check_empty_last_line()
307+ checker.check_empty_last_line(3)
308 expected = [(
309 3, 'File does not ends with an empty line.')]
310 self.assertEqual(expected, self.reporter.messages)
311
312=== modified file 'pocketlint/tests/test_text.py'
313--- pocketlint/tests/test_text.py 2012-01-29 21:24:20 +0000
314+++ pocketlint/tests/test_text.py 2012-12-01 20:25:22 +0000
315@@ -75,3 +75,49 @@
316 self.assertEqual(
317 [(1, 'Line exceeds 49 characters.')],
318 self.reporter.messages)
319+
320+ def test_no_empty_last_line(self):
321+ """
322+ An error is reported if file does not end with a new lines.
323+ """
324+ content = (
325+ 'Some first line\n'
326+ 'the second and last line witout newline'
327+ )
328+ checker = AnyTextChecker('bogus', content, self.reporter)
329+ checker.check_empty_last_line(2)
330+ expected = [(
331+ 2, 'File does not ends with an empty line.')]
332+ self.assertEqual(expected, self.reporter.messages)
333+ self.assertEqual(1, self.reporter.call_count)
334+
335+ def test_multiple_empty_last_lines(self):
336+ """
337+ An error is reported if file ends with multiple new lines.
338+ """
339+ content = (
340+ 'Some first line\n'
341+ 'the second and last\n'
342+ '\n'
343+ )
344+ checker = AnyTextChecker('bogus', content, self.reporter)
345+ checker.check_empty_last_line(3)
346+ expected = [(
347+ 3, 'File does not ends with an empty line.')]
348+ self.assertEqual(expected, self.reporter.messages)
349+ self.assertEqual(1, self.reporter.call_count)
350+
351+ def test_single_last_line_no_newline(self):
352+ """
353+ An error is reported if file contains a single line and does not end
354+ with a new lines.
355+ """
356+ content = (
357+ 'the second and last line witout newline'
358+ )
359+ checker = AnyTextChecker('bogus', content, self.reporter)
360+ checker.check_empty_last_line(2)
361+ expected = [(
362+ 2, 'File does not ends with an empty line.')]
363+ self.assertEqual(expected, self.reporter.messages)
364+ self.assertEqual(1, self.reporter.call_count)

Subscribers

People subscribed via source and target branches

to all changes: