Merge lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21 into lp:ubuntu/oneiric/pocket-lint
- Oneiric (11.10)
- 0.5.21
- Merge into oneiric
Proposed by
Curtis Hovey
Status: | Superseded | ||||||||
---|---|---|---|---|---|---|---|---|---|
Proposed branch: | lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21 | ||||||||
Merge into: | lp:ubuntu/oneiric/pocket-lint | ||||||||
Diff against target: |
2449 lines (+1499/-241) 10 files modified
ChangeLog (+121/-0) PKG-INFO (+1/-1) debian/changelog (+6/-0) debian/control (+1/-2) debian/copyright (+6/-1) pocketlint/contrib/cssccc.py (+495/-0) pocketlint/contrib/pep8.py (+683/-188) pocketlint/formatcheck.py (+94/-34) pocketlint/jsreporter.js (+71/-14) setup.py (+21/-1) |
||||||||
To merge this branch: | bzr merge lp:~sinzui/ubuntu/oneiric/pocket-lint/0.5.21 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Needs Fixing | ||
Ubuntu branches | Pending | ||
Review via email: mp+74924@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-10-18.
Commit message
Description of the change
Merge upstream release that improves CSS and JS support
debian/control was updated to require gjs
To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote : | # |
oh, and just to be clear, you're not expecting this for SRU to oneiric, right?
Revision history for this message
Curtis Hovey (sinzui) wrote : | # |
Thank you for spotting the license change. I updated the license file. No I do not think this deserves an SRU given that the branch is more than a month old.
- 6. By Curtis Hovey
-
Updated license information.
Unmerged revisions
- 6. By Curtis Hovey
-
Updated license information.
- 5. By Curtis Hovey
-
New upstream release improves CSS and JS support.
Fixes Gtk3 bugs.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'ChangeLog' |
2 | --- ChangeLog 2011-04-16 15:32:48 +0000 |
3 | +++ ChangeLog 2011-10-14 21:24:26 +0000 |
4 | @@ -1,5 +1,126 @@ |
5 | <Generated by bzr log --log-format=gnu> |
6 | |
7 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
8 | + |
9 | + [396] Use seed as the js interpreter. |
10 | + |
11 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
12 | + |
13 | + [395] Clear refernece. |
14 | + |
15 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
16 | + |
17 | + [394] Documented how to use jsreporter.js |
18 | + |
19 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
20 | + |
21 | + [393] Removed unused code. |
22 | + |
23 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
24 | + |
25 | + [392] Use seed as the default js command. |
26 | + |
27 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
28 | + |
29 | + [391] Updated attr name. |
30 | + |
31 | +2011-06-06 Curtis Hovey <sinzui.is@verizon.net> |
32 | + |
33 | + [390] Removed js subprocess, but HTML5Browser is slow. |
34 | + |
35 | +2011-06-05 Curtis Hovey <sinzui.is@verizon.net> |
36 | + |
37 | + [389] Updated documentation. |
38 | + |
39 | +2011-06-05 Curtis Hovey <sinzui.is@verizon.net> |
40 | + |
41 | + [388] Minor speed changes. |
42 | + |
43 | +2011-06-05 Curtis Hovey <sinzui.is@verizon.net> |
44 | + |
45 | + [387] Added rudimentary JS checker based on Webkit. |
46 | + |
47 | +2011-05-10 Curtis Hovey <sinzui.is@verizon.net> |
48 | + |
49 | + [386] Incremented version. |
50 | + |
51 | +2011-05-10 Curtis Hovey <sinzui.is@verizon.net> |
52 | + |
53 | + [385] merged Adi's fix. |
54 | + |
55 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
56 | + |
57 | + [384] Inremented version. |
58 | + |
59 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
60 | + |
61 | + [383] Corrected the js line number. |
62 | + |
63 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
64 | + |
65 | + [382] Catch and report IndentationExceptions. |
66 | + |
67 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
68 | + |
69 | + [381] Removed unneeded checks. |
70 | + |
71 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
72 | + |
73 | + [380] Remove trailing whitespace. |
74 | + |
75 | +2011-05-04 Curtis Hovey <sinzui.is@verizon.net> |
76 | + |
77 | + [379] Merged python encoding fix. |
78 | + |
79 | +2011-04-19 Curtis Hovey <sinzui.is@verizon.net> |
80 | + |
81 | + [378] Increment version. |
82 | + |
83 | +2011-04-19 Curtis Hovey <sinzui.is@verizon.net> |
84 | + |
85 | + [377] Hush WindowError. |
86 | + |
87 | +2011-04-19 Curtis Hovey <sinzui.is@verizon.net> |
88 | + |
89 | + [376] Move signed archives rules into setup.py. |
90 | + |
91 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
92 | + |
93 | + [375] Incremented version for cssccc |
94 | + |
95 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
96 | + |
97 | + [374] Merged cssccc lib. |
98 | + |
99 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
100 | + |
101 | + [373] Added a rule to sign the release after it is made. |
102 | + |
103 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
104 | + |
105 | + [372] Incremenet version for release. |
106 | + |
107 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
108 | + |
109 | + [371] Merged Adi's feature that returns the count od reported issues. |
110 | + |
111 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
112 | + |
113 | + [370] Added simple SQL checker. |
114 | + |
115 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
116 | + |
117 | + [369] Elaborated comment test. |
118 | + |
119 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
120 | + |
121 | + [368] Added a basic test to verify comments do not mess up blank line |
122 | + counts. |
123 | + |
124 | +2011-04-17 Curtis Hovey <sinzui.is@verizon.net> |
125 | + |
126 | + [367] Update pep8 to version 0.6.1 |
127 | + |
128 | 2011-04-15 Curtis Hovey <sinzui.is@verizon.net> |
129 | |
130 | [366] Increment version. |
131 | |
132 | === modified file 'PKG-INFO' |
133 | --- PKG-INFO 2011-04-16 15:32:48 +0000 |
134 | +++ PKG-INFO 2011-10-14 21:24:26 +0000 |
135 | @@ -1,6 +1,6 @@ |
136 | Metadata-Version: 1.0 |
137 | Name: pocketlint |
138 | -Version: 0.5.13 |
139 | +Version: 0.5.21 |
140 | Summary: Pocket-lint a composite linter and style checker. |
141 | Home-page: https://launchpad.net/pocket-lint |
142 | Author: Curtis C. Hovey |
143 | |
144 | === modified file 'debian/changelog' |
145 | --- debian/changelog 2011-04-16 15:32:48 +0000 |
146 | +++ debian/changelog 2011-10-14 21:24:26 +0000 |
147 | @@ -1,3 +1,9 @@ |
148 | +pocket-lint (0.5.21-0ubuntu1) UNRELEASED; urgency=low |
149 | + |
150 | + * New upstream release. |
151 | + |
152 | + -- Curtis C. Hovey <sinzui.is@verizon.net> Sun, 11 Sep 2011 17:22:02 -0400 |
153 | + |
154 | pocket-lint (0.5.13-0ubuntu1) natty; urgency=low |
155 | |
156 | * New upstream bug fix release |
157 | |
158 | === modified file 'debian/control' |
159 | --- debian/control 2011-04-15 13:37:44 +0000 |
160 | +++ debian/control 2011-10-14 21:24:26 +0000 |
161 | @@ -11,8 +11,7 @@ |
162 | Package: python-pocket-lint |
163 | Architecture: all |
164 | Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends} |
165 | -Recommends: python-cssutils |
166 | -Suggests: spidermonkey-bin |
167 | +Recommends: python-cssutils, gjs |
168 | Description: a composite linter and style checker |
169 | Pocket-lint has several notable features: |
170 | . |
171 | |
172 | === modified file 'debian/copyright' |
173 | --- debian/copyright 2010-11-17 15:46:54 +0000 |
174 | +++ debian/copyright 2011-10-14 21:24:26 +0000 |
175 | @@ -4,7 +4,7 @@ |
176 | Copyright: |
177 | Upstream Author: Curtis C. Hovey |
178 | |
179 | -License: |
180 | +Primary license: |
181 | The MIT License: |
182 | |
183 | Permission is hereby granted, free of charge, to any person obtaining a copy |
184 | @@ -25,6 +25,11 @@ |
185 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
186 | THE SOFTWARE. |
187 | |
188 | + |
189 | +contrib/cssccc.py license: |
190 | + Public Domain |
191 | + |
192 | + |
193 | The Debian packaging is: |
194 | Copyright 2010 Curtis C. Hovey |
195 | and is under the MIT licence. |
196 | |
197 | === added file 'pocketlint/contrib/cssccc.py' |
198 | --- pocketlint/contrib/cssccc.py 1970-01-01 00:00:00 +0000 |
199 | +++ pocketlint/contrib/cssccc.py 2011-10-14 21:24:26 +0000 |
200 | @@ -0,0 +1,495 @@ |
201 | +''' |
202 | +This code is in the public domain. |
203 | + |
204 | +Check CSS code for some common coding conventions. |
205 | +The code must be in a valid CSS format. |
206 | +It is recommend to first parse it using cssutils. |
207 | +It is also recommend to check it with pocket-lint for things like trailing |
208 | +spaces or tab characters. |
209 | + |
210 | +If a comment is on the whole line, it will consume the whole line like it |
211 | +was not there. |
212 | +If a comment is inside a line it will only consume its own content. |
213 | + |
214 | +Bases on Stoyan Stefanov's http://www.phpied.com/css-coding-conventions/ |
215 | + |
216 | +'@media' rule is not supported. |
217 | + @media print { |
218 | + html { |
219 | + background: #fff; |
220 | + color: #000; |
221 | + } |
222 | + body { |
223 | + padding: 1in; |
224 | + border: 0.5pt solid #666; |
225 | + } |
226 | + } |
227 | + |
228 | +The following at-rules are supported: |
229 | + * keyword / text at-rules |
230 | + * @charset "ISO-8859-15"; |
231 | + * @import url(/css/screen.css) screen, projection; |
232 | + * @namespace foo "http://example.com/ns/foo"; |
233 | + * keybord / block rules |
234 | + * @page { block; } |
235 | + * @font-face { block; } |
236 | + |
237 | + |
238 | +TODO: |
239 | + * add warning for using px for fonts. |
240 | + * add Unicode support. |
241 | + * add AtRule checks |
242 | + * add support for TAB as a separator / identation. |
243 | + * add support for @media |
244 | +''' |
245 | +from __future__ import with_statement |
246 | + |
247 | +__version__ = '0.1.1' |
248 | + |
249 | +import sys |
250 | + |
251 | +SELECTOR_SEPARATOR = ',' |
252 | +DECLARATION_SEPARATOR = ';' |
253 | +PROPERTY_SEPARATOR = ':' |
254 | +COMMENT_START = r'/*' |
255 | +COMMENT_END = r'*/' |
256 | +AT_TEXT_RULES = ['import', 'charset', 'namespace'] |
257 | +AT_BLOCK_RULES = ['page', 'font-face'] |
258 | +# If you want |
259 | +# selector, |
260 | +# selector2 |
261 | +# { |
262 | +# property: |
263 | +# } |
264 | +#IGNORED_MESSAGES = ['I013', 'I014'] |
265 | + |
266 | +# If you want |
267 | +# selector, |
268 | +# selector { |
269 | +# property: |
270 | +# } |
271 | +#IGNORED_MESSAGES = ['I005', 'I014'] |
272 | + |
273 | +# If you want |
274 | +# selector, |
275 | +# selector2 { |
276 | +# property: |
277 | +# } |
278 | +IGNORED_MESSAGES = ['I005', 'I006'] |
279 | + |
280 | + |
281 | +class CSSRule(object): |
282 | + '''A CSS rule.''' |
283 | + |
284 | + def check(self): |
285 | + '''Check the rule.''' |
286 | + raise AssertionError('Method not implemtned.') |
287 | + |
288 | + |
289 | +class CSSAtRule(object): |
290 | + '''A CSS @rule.''' |
291 | + |
292 | + type = object() |
293 | + |
294 | + def __init__(self, identifier, keyword, log, text=None, block=None): |
295 | + self.identifier = identifier |
296 | + self.keyword = keyword |
297 | + self.text = text |
298 | + self.block = block |
299 | + self.log = log |
300 | + |
301 | + def check(self): |
302 | + '''Check the rule.''' |
303 | + |
304 | + |
305 | +class CSSRuleSet(object): |
306 | + '''A CSS rule_set.''' |
307 | + |
308 | + type = object() |
309 | + |
310 | + def __init__(self, selector, declarations, log): |
311 | + self.selector = selector |
312 | + self.declarations = declarations |
313 | + self.log = log |
314 | + |
315 | + def __str__(self): |
316 | + return '%s{%s}' % (str(self.selector), str(self.declarations)) |
317 | + |
318 | + def __repr__(self): |
319 | + return '%d:%s{%s}' % ( |
320 | + self.selector.start_line, |
321 | + str(self.selector), |
322 | + str(self.declarations), |
323 | + ) |
324 | + |
325 | + def check(self): |
326 | + '''Check the rule set.''' |
327 | + self.checkSelector() |
328 | + self.checkDeclarations() |
329 | + |
330 | + def checkSelector(self): |
331 | + '''Check rule-set selector.''' |
332 | + start_line = self.selector.getStartLine() |
333 | + selectors = self.selector.text.split(SELECTOR_SEPARATOR) |
334 | + offset = 0 |
335 | + last_selector = selectors[-1] |
336 | + first_selector = selectors[0] |
337 | + rest_selectors = selectors[1:] |
338 | + |
339 | + if first_selector.startswith('\n\n\n'): |
340 | + self.log(start_line, 'I002', 'To many newlines before selectors.') |
341 | + elif first_selector.startswith('\n\n'): |
342 | + pass |
343 | + elif start_line > 2: |
344 | + self.log(start_line, 'I003', 'To few newlines before selectors.') |
345 | + else: |
346 | + pass |
347 | + |
348 | + for selector in rest_selectors: |
349 | + if not selector.startswith('\n'): |
350 | + self.log( |
351 | + start_line + offset, |
352 | + 'I004', |
353 | + 'Selector must be on a new line.') |
354 | + offset += selector.count('\n') |
355 | + |
356 | + if not last_selector.endswith('\n'): |
357 | + self.log( |
358 | + start_line + offset, |
359 | + 'I005', |
360 | + 'No newline after last selector.') |
361 | + |
362 | + if not (last_selector[-2] != ' ' and last_selector[-1] == (' ')): |
363 | + self.log( |
364 | + start_line + offset, |
365 | + 'I013', |
366 | + 'Last selector must be followed by " {".') |
367 | + |
368 | + def checkDeclarations(self): |
369 | + '''Check rule-set declarations.''' |
370 | + start_line = self.declarations.getStartLine() |
371 | + declarations = self.declarations.text.split(DECLARATION_SEPARATOR) |
372 | + offset = 0 |
373 | + |
374 | + # Check all declarations except last as this is the new line. |
375 | + first_declaration = True |
376 | + for declaration in declarations[:-1]: |
377 | + if not declaration.startswith('\n'): |
378 | + self.log( |
379 | + start_line + offset, |
380 | + 'I007', |
381 | + 'Each declarations should start on a new line.', |
382 | + ) |
383 | + elif (not declaration.startswith('\n ') or |
384 | + declaration[5] == ' '): |
385 | + self.log( |
386 | + start_line + offset, |
387 | + 'I008', |
388 | + 'Each declaration must be indented with 4 spaces.', |
389 | + ) |
390 | + |
391 | + parts = declaration.split(PROPERTY_SEPARATOR) |
392 | + if len(parts) != 2: |
393 | + self.log( |
394 | + start_line + offset, |
395 | + 'I009', |
396 | + 'Wrong separator on property: value pair.', |
397 | + ) |
398 | + else: |
399 | + prop, value = parts |
400 | + if prop.endswith(' '): |
401 | + self.log( |
402 | + start_line + offset, |
403 | + 'I010', |
404 | + 'Whitespace before ":".', |
405 | + ) |
406 | + if not (value.startswith(' ') or value.startswith('\n')): |
407 | + self.log( |
408 | + start_line + offset, |
409 | + 'I011', |
410 | + 'Missing whitespace after ":".', |
411 | + ) |
412 | + elif value.startswith(' '): |
413 | + self.log( |
414 | + start_line + offset, |
415 | + 'I012', |
416 | + 'Multiple whitespaces after ":".', |
417 | + ) |
418 | + if first_declaration: |
419 | + first_declaration = False |
420 | + else: |
421 | + offset += declaration.count('\n') |
422 | + |
423 | + last_declaration = declarations[-1] |
424 | + offset += last_declaration.count('\n') |
425 | + if last_declaration != '\n': |
426 | + self.log( |
427 | + start_line + offset, |
428 | + 'I006', |
429 | + 'Rule declarations should end with a single new line.', |
430 | + ) |
431 | + if last_declaration != '\n ': |
432 | + self.log( |
433 | + start_line + offset, |
434 | + 'I014', |
435 | + 'Rule declarations should end indented on a single new line.', |
436 | + ) |
437 | + |
438 | + |
439 | +class CSSStatementMember(object): |
440 | + '''A member of CSS statement.''' |
441 | + |
442 | + def __init__(self, start_line, start_character, text): |
443 | + self.start_line = start_line |
444 | + self.start_character = start_character |
445 | + self.text = text |
446 | + |
447 | + def getStartLine(self): |
448 | + '''Return the line number for first character in the statement and |
449 | + the number of new lines untilg the first character.''' |
450 | + index = 0 |
451 | + text = self.text |
452 | + character = text[index] |
453 | + while character == '\n': |
454 | + index += 1 |
455 | + character = text[index] |
456 | + |
457 | + return self.start_line + index + 1 |
458 | + |
459 | + def __str__(self): |
460 | + return self.text |
461 | + |
462 | + def __repr__(self): |
463 | + return '%d:%d:{%s}' % ( |
464 | + self.start_line, self.start_character, self.text) |
465 | + |
466 | + |
467 | +class CSSCodingConventionChecker(object): |
468 | + '''CSS coding convention checker.''' |
469 | + |
470 | + icons = { |
471 | + 'E': 'error', |
472 | + 'I': 'info', |
473 | + } |
474 | + |
475 | + def __init__(self, text, logger=None): |
476 | + self._text = text.splitlines(True) |
477 | + self.line_number = 0 |
478 | + self.character_number = 0 |
479 | + if logger: |
480 | + self._logger = logger |
481 | + else: |
482 | + self._logger = self._defaultLog |
483 | + |
484 | + def log(self, line_number, code, message): |
485 | + '''Log the message with `code`.''' |
486 | + if code in IGNORED_MESSAGES: |
487 | + return |
488 | + icon = self.icons[code[0]] |
489 | + self._logger(line_number, code + ': ' + message, icon=icon) |
490 | + |
491 | + def check(self): |
492 | + '''Check all rules.''' |
493 | + for rule in self.getRules(): |
494 | + rule.check() |
495 | + |
496 | + def getRules(self): |
497 | + '''Generates the next CSS rule ignoring comments.''' |
498 | + while True: |
499 | + yield self.getNextRule() |
500 | + |
501 | + def getNextRule(self): |
502 | + '''Return the next parsed rule. |
503 | + |
504 | + Raise `StopIteration` if we are at the last rule. |
505 | + ''' |
506 | + if self._nextStatementIsAtRule(): |
507 | + text = None |
508 | + block = None |
509 | + keyword = self._parse('@') |
510 | + # TODO: user regex [ \t {] |
511 | + keyword_text = self._parse(' ') |
512 | + keyword_name = keyword_text.text |
513 | + keyword.text += '@' + keyword_name + ' ' |
514 | + |
515 | + if keyword_name.lower() in AT_TEXT_RULES: |
516 | + text = self._parse(';') |
517 | + elif keyword_name.lower() in AT_BLOCK_RULES: |
518 | + start = self._parse('{') |
519 | + keyword.text += start.text |
520 | + block = self._parse('}') |
521 | + else: |
522 | + self._parse(';') |
523 | + raise StopIteration |
524 | + |
525 | + return CSSAtRule( |
526 | + identifier=keyword_name, |
527 | + keyword=keyword, |
528 | + text=text, |
529 | + block=block, |
530 | + log=self.log) |
531 | + else: |
532 | + selector = self._parse('{') |
533 | + declarations = self._parse('}') |
534 | + return CSSRuleSet( |
535 | + selector=selector, |
536 | + declarations=declarations, |
537 | + log=self.log) |
538 | + |
539 | + def _defaultLog(self, line_number, message, icon='info'): |
540 | + '''Log the message to STDOUT.''' |
541 | + print ' %4s:%s' % (line_number, message) |
542 | + |
543 | + def _nextStatementIsAtRule(self): |
544 | + '''Return True if next statement in the buffer is an at-rule. |
545 | + |
546 | + Just look for open brackets and see if there is an @ before that |
547 | + braket. |
548 | + ''' |
549 | + search_buffer = [] |
550 | + line_counter = self.line_number |
551 | + current_line = self._text[line_counter][self.character_number:] |
552 | + while current_line.find('@') == -1: |
553 | + search_buffer.append(current_line) |
554 | + line_counter += 1 |
555 | + try: |
556 | + current_line = self._text[line_counter] |
557 | + except IndexError: |
558 | + return False |
559 | + |
560 | + text_buffer = ''.join(search_buffer) |
561 | + if text_buffer.find('{') == -1: |
562 | + return True |
563 | + else: |
564 | + return False |
565 | + |
566 | + def _parse(self, stop_character): |
567 | + '''Return the parsed text until stop_character.''' |
568 | + try: |
569 | + self._text[self.line_number][self.character_number] |
570 | + except IndexError: |
571 | + raise StopIteration |
572 | + result = [] |
573 | + start_line = self.line_number |
574 | + start_character = self.character_number |
575 | + comment_started = False |
576 | + while True: |
577 | + try: |
578 | + data = self._text[self.line_number][self.character_number:] |
579 | + except IndexError: |
580 | + break |
581 | + |
582 | + # Look for comment start/end. |
583 | + (comment_update, |
584 | + before_comment, |
585 | + after_comment, |
586 | + newline_consumed) = _check_comment(data) |
587 | + if comment_update is not None: |
588 | + comment_started = comment_update |
589 | + |
590 | + if comment_started: |
591 | + # We are inside a comment. |
592 | + # Add the data before the comment and go to next line. |
593 | + if before_comment is not None: |
594 | + result.append(before_comment) |
595 | + self.character_number = 0 |
596 | + self.line_number += 1 |
597 | + continue |
598 | + |
599 | + # If we have a comment, strip it from the data. |
600 | + # Remember the initial cursor position to know where to |
601 | + # continue. |
602 | + initial_position = data.find(stop_character) |
603 | + if before_comment is not None or after_comment is not None: |
604 | + if before_comment is None: |
605 | + before_comment = '' |
606 | + if after_comment is None: |
607 | + after_comment = '' |
608 | + data = before_comment + after_comment |
609 | + |
610 | + if initial_position == -1 or newline_consumed: |
611 | + # We are not at the end. |
612 | + # Go to next line and append the data. |
613 | + result.append(data) |
614 | + self.character_number = 0 |
615 | + self.line_number += 1 |
616 | + continue |
617 | + else: |
618 | + # Delimiter found. |
619 | + # Find it again in the text that now has no comments. |
620 | + # Append data until the delimiter. |
621 | + # Move cursor to next character and stop searching for it. |
622 | + new_position = data.find(stop_character) |
623 | + result.append(data[:new_position]) |
624 | + self.character_number += initial_position + 1 |
625 | + break |
626 | + |
627 | + return CSSStatementMember( |
628 | + start_line=start_line, |
629 | + start_character=start_character, |
630 | + text=''.join(result)) |
631 | + |
632 | + |
633 | +def _check_comment(data): |
634 | + '''Check the data for comment markers.''' |
635 | + |
636 | + comment_started = None |
637 | + before_comment = None |
638 | + after_comment = None |
639 | + newline_consumed = False |
640 | + |
641 | + comment_start = data.find(COMMENT_START) |
642 | + if comment_start != -1: |
643 | + comment_started = True |
644 | + before_comment = data[:comment_start] |
645 | + # Only use `None` to signal that there is no text before the comment. |
646 | + if before_comment == '': |
647 | + before_comment = None |
648 | + |
649 | + comment_end = data.find(COMMENT_END) |
650 | + if comment_end != -1: |
651 | + comment_started = False |
652 | + # Set comment end after the lenght of the actual comment end |
653 | + # marker. |
654 | + comment_end += len(COMMENT_END) |
655 | + if before_comment is None and data[comment_end] == '\n': |
656 | + # Consume the new line if it next to the comment end and |
657 | + # the comment in on the whole line. |
658 | + comment_end += 1 |
659 | + newline_consumed = True |
660 | + after_comment = data[comment_end:] |
661 | + return (comment_started, before_comment, after_comment, newline_consumed) |
662 | + |
663 | + |
664 | +def show_usage(): |
665 | + '''Print the command usage.''' |
666 | + print 'Usage: cssccc OPTIONS' |
667 | + print ' -h, --help\t\tShow this help.' |
668 | + print ' -v, --version\t\tShow version.' |
669 | + print ' -f FILE, --file=FILE\tCheck FILE' |
670 | + |
671 | + |
672 | +def read_file(filename): |
673 | + '''Return the content of filename.''' |
674 | + text = '' |
675 | + with open(filename, 'r') as f: |
676 | + text = f.read() |
677 | + return text |
678 | + |
679 | + |
680 | +if __name__ == '__main__': |
681 | + if len(sys.argv) < 2: |
682 | + show_usage() |
683 | + elif sys.argv[1] in ['-v', '--version']: |
684 | + print 'CSS Code Convention Checker %s' % (__version__) |
685 | + sys.exit(0) |
686 | + elif sys.argv[1] == '-f': |
687 | + text = read_file(sys.argv[2]) |
688 | + checker = CSSCodingConventionChecker(text) |
689 | + sys.exit(checker.check()) |
690 | + elif sys.argv[1] == '--file=': |
691 | + text = read_file(sys.argv[1][len('--file='):]) |
692 | + checker = CSSCodingConventionChecker(text) |
693 | + sys.exit(checker.check()) |
694 | + else: |
695 | + show_usage() |
696 | |
697 | === modified file 'pocketlint/contrib/pep8.py' |
698 | --- pocketlint/contrib/pep8.py 2010-11-17 15:46:54 +0000 |
699 | +++ pocketlint/contrib/pep8.py 2011-10-14 21:24:26 +0000 |
700 | @@ -1,6 +1,6 @@ |
701 | #!/usr/bin/python |
702 | # pep8.py - Check Python source code formatting, according to PEP 8 |
703 | -# Copyright (C) 2006 Johann C. Rocholl <johann@browsershots.org> |
704 | +# Copyright (C) 2006 Johann C. Rocholl <johann@rocholl.net> |
705 | # |
706 | # Permission is hereby granted, free of charge, to any person |
707 | # obtaining a copy of this software and associated documentation files |
708 | @@ -30,8 +30,7 @@ |
709 | $ python pep8.py -h |
710 | |
711 | This program and its regression test suite live here: |
712 | -http://svn.browsershots.org/trunk/devtools/pep8/ |
713 | -http://trac.browsershots.org/browser/trunk/devtools/pep8/ |
714 | +http://github.com/jcrocholl/pep8 |
715 | |
716 | Groups of errors and warnings: |
717 | E errors |
718 | @@ -79,33 +78,65 @@ |
719 | |
720 | The docstring of each check function shall be the relevant part of |
721 | text from PEP 8. It is printed if the user enables --show-pep8. |
722 | +Several docstrings contain examples directly from the PEP 8 document. |
723 | + |
724 | +Okay: spam(ham[1], {eggs: 2}) |
725 | +E201: spam( ham[1], {eggs: 2}) |
726 | + |
727 | +These examples are verified automatically when pep8.py is run with the |
728 | +--doctest option. You can add examples for your own check functions. |
729 | +The format is simple: "Okay" or error/warning code followed by colon |
730 | +and space, the rest of the line is example source code. If you put 'r' |
731 | +before the docstring, you can use \n for newline, \t for tab and \s |
732 | +for space. |
733 | |
734 | """ |
735 | |
736 | +__version__ = '0.5.1dev' |
737 | + |
738 | import os |
739 | import sys |
740 | import re |
741 | import time |
742 | import inspect |
743 | +import keyword |
744 | import tokenize |
745 | from optparse import OptionParser |
746 | -from keyword import iskeyword |
747 | from fnmatch import fnmatch |
748 | - |
749 | -__version__ = '0.2.0' |
750 | -__revision__ = '$Rev: 2208 $' |
751 | - |
752 | -default_exclude = '.svn,CVS,*.pyc,*.pyo' |
753 | - |
754 | -indent_match = re.compile(r'([ \t]*)').match |
755 | -raise_comma_match = re.compile(r'raise\s+\w+\s*(,)').match |
756 | - |
757 | -operators = """ |
758 | -+ - * / % ^ & | = < > >> << |
759 | -+= -= *= /= %= ^= &= |= == <= >= >>= <<= |
760 | -!= <> : |
761 | -in is or not and |
762 | -""".split() |
763 | +try: |
764 | + frozenset |
765 | +except NameError: |
766 | + from sets import ImmutableSet as frozenset |
767 | + |
768 | + |
769 | +DEFAULT_EXCLUDE = '.svn,CVS,.bzr,.hg,.git' |
770 | +DEFAULT_IGNORE = 'E24' |
771 | +MAX_LINE_LENGTH = 79 |
772 | + |
773 | +INDENT_REGEX = re.compile(r'([ \t]*)') |
774 | +RAISE_COMMA_REGEX = re.compile(r'raise\s+\w+\s*(,)') |
775 | +SELFTEST_REGEX = re.compile(r'(Okay|[EW]\d{3}):\s(.*)') |
776 | +ERRORCODE_REGEX = re.compile(r'[EW]\d{3}') |
777 | +DOCSTRING_REGEX = re.compile(r'u?r?["\']') |
778 | +WHITESPACE_AROUND_OPERATOR_REGEX = \ |
779 | + re.compile('([^\w\s]*)\s*(\t| )\s*([^\w\s]*)') |
780 | +EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]') |
781 | +WHITESPACE_AROUND_NAMED_PARAMETER_REGEX = \ |
782 | + re.compile(r'[()]|\s=[^=]|[^=!<>]=\s') |
783 | + |
784 | + |
785 | +WHITESPACE = ' \t' |
786 | + |
787 | +BINARY_OPERATORS = frozenset(['**=', '*=', '+=', '-=', '!=', '<>', |
788 | + '%=', '^=', '&=', '|=', '==', '/=', '//=', '<=', '>=', '<<=', '>>=', |
789 | + '%', '^', '&', '|', '=', '/', '//', '<', '>', '<<']) |
790 | +UNARY_OPERATORS = frozenset(['>>', '**', '*', '+', '-']) |
791 | +OPERATORS = BINARY_OPERATORS | UNARY_OPERATORS |
792 | +SKIP_TOKENS = frozenset([tokenize.COMMENT, tokenize.NL, tokenize.INDENT, |
793 | + tokenize.DEDENT, tokenize.NEWLINE]) |
794 | +E225NOT_KEYWORDS = (frozenset(keyword.kwlist + ['print']) - |
795 | + frozenset(['False', 'None', 'True'])) |
796 | +BENCHMARK_KEYS = ('directories', 'files', 'logical lines', 'physical lines') |
797 | |
798 | options = None |
799 | args = None |
800 | @@ -117,7 +148,7 @@ |
801 | |
802 | |
803 | def tabs_or_spaces(physical_line, indent_char): |
804 | - """ |
805 | + r""" |
806 | Never mix tabs and spaces. |
807 | |
808 | The most popular way of indenting Python is with spaces only. The |
809 | @@ -126,38 +157,65 @@ |
810 | invoking the Python command line interpreter with the -t option, it issues |
811 | warnings about code that illegally mixes tabs and spaces. When using -tt |
812 | these warnings become errors. These options are highly recommended! |
813 | + |
814 | + Okay: if a == 0:\n a = 1\n b = 1 |
815 | + E101: if a == 0:\n a = 1\n\tb = 1 |
816 | """ |
817 | - indent = indent_match(physical_line).group(1) |
818 | + indent = INDENT_REGEX.match(physical_line).group(1) |
819 | for offset, char in enumerate(indent): |
820 | if char != indent_char: |
821 | return offset, "E101 indentation contains mixed spaces and tabs" |
822 | |
823 | |
824 | def tabs_obsolete(physical_line): |
825 | - """ |
826 | + r""" |
827 | For new projects, spaces-only are strongly recommended over tabs. Most |
828 | editors have features that make this easy to do. |
829 | + |
830 | + Okay: if True:\n return |
831 | + W191: if True:\n\treturn |
832 | """ |
833 | - indent = indent_match(physical_line).group(1) |
834 | + indent = INDENT_REGEX.match(physical_line).group(1) |
835 | if indent.count('\t'): |
836 | return indent.index('\t'), "W191 indentation contains tabs" |
837 | |
838 | |
839 | def trailing_whitespace(physical_line): |
840 | - """ |
841 | + r""" |
842 | JCR: Trailing whitespace is superfluous. |
843 | + FBM: Except when it occurs as part of a blank line (i.e. the line is |
844 | + nothing but whitespace). According to Python docs[1] a line with only |
845 | + whitespace is considered a blank line, and is to be ignored. However, |
846 | + matching a blank line to its indentation level avoids mistakenly |
847 | + terminating a multi-line statement (e.g. class declaration) when |
848 | + pasting code into the standard Python interpreter. |
849 | + |
850 | + [1] http://docs.python.org/reference/lexical_analysis.html#blank-lines |
851 | + |
852 | + The warning returned varies on whether the line itself is blank, for easier |
853 | + filtering for those who want to indent their blank lines. |
854 | + |
855 | + Okay: spam(1) |
856 | + W291: spam(1)\s |
857 | + W293: class Foo(object):\n \n bang = 12 |
858 | """ |
859 | - physical_line = physical_line.rstrip('\n') # chr(10), newline |
860 | - physical_line = physical_line.rstrip('\r') # chr(13), carriage return |
861 | - physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L |
862 | + physical_line = physical_line.rstrip('\n') # chr(10), newline |
863 | + physical_line = physical_line.rstrip('\r') # chr(13), carriage return |
864 | + physical_line = physical_line.rstrip('\x0c') # chr(12), form feed, ^L |
865 | stripped = physical_line.rstrip() |
866 | if physical_line != stripped: |
867 | - return len(stripped), "W291 trailing whitespace" |
868 | + if stripped: |
869 | + return len(stripped), "W291 trailing whitespace" |
870 | + else: |
871 | + return 0, "W293 blank line contains whitespace" |
872 | |
873 | |
874 | def trailing_blank_lines(physical_line, lines, line_number): |
875 | - """ |
876 | + r""" |
877 | JCR: Trailing blank lines are superfluous. |
878 | + |
879 | + Okay: spam(1) |
880 | + W391: spam(1)\n |
881 | """ |
882 | if physical_line.strip() == '' and line_number == len(lines): |
883 | return 0, "W391 blank line at end of file" |
884 | @@ -182,9 +240,18 @@ |
885 | For flowing long blocks of text (docstrings or comments), limiting the |
886 | length to 72 characters is recommended. |
887 | """ |
888 | - length = len(physical_line.rstrip()) |
889 | - if length > 79: |
890 | - return 79, "E501 line too long (%d characters)" % length |
891 | + line = physical_line.rstrip() |
892 | + length = len(line) |
893 | + if length > MAX_LINE_LENGTH: |
894 | + try: |
895 | + # The line could contain multi-byte characters |
896 | + if not hasattr(line, 'decode'): # Python 3 |
897 | + line = line.encode('latin-1') |
898 | + length = len(line.decode('utf-8')) |
899 | + except UnicodeDecodeError: |
900 | + pass |
901 | + if length > MAX_LINE_LENGTH: |
902 | + return MAX_LINE_LENGTH, "E501 line too long (%d characters)" % length |
903 | |
904 | |
905 | ############################################################################## |
906 | @@ -193,8 +260,9 @@ |
907 | |
908 | |
909 | def blank_lines(logical_line, blank_lines, indent_level, line_number, |
910 | - previous_logical): |
911 | - """ |
912 | + previous_logical, previous_indent_level, |
913 | + blank_lines_before_comment): |
914 | + r""" |
915 | Separate top-level function and class definitions with two blank lines. |
916 | |
917 | Method definitions inside a class are separated by a single blank line. |
918 | @@ -204,20 +272,33 @@ |
919 | one-liners (e.g. a set of dummy implementations). |
920 | |
921 | Use blank lines in functions, sparingly, to indicate logical sections. |
922 | + |
923 | + Okay: def a():\n pass\n\n\ndef b():\n pass |
924 | + Okay: def a():\n pass\n\n\n# Foo\n# Bar\n\ndef b():\n pass |
925 | + |
926 | + E301: class Foo:\n b = 0\n def bar():\n pass |
927 | + E302: def a():\n pass\n\ndef b(n):\n pass |
928 | + E303: def a():\n pass\n\n\n\ndef b(n):\n pass |
929 | + E303: def a():\n\n\n\n pass |
930 | + E304: @decorator\n\ndef a():\n pass |
931 | """ |
932 | if line_number == 1: |
933 | - return # Don't expect blank lines before the first line |
934 | + return # Don't expect blank lines before the first line |
935 | + max_blank_lines = max(blank_lines, blank_lines_before_comment) |
936 | if previous_logical.startswith('@'): |
937 | - return # Don't expect blank lines after function decorator |
938 | - if (logical_line.startswith('def ') or |
939 | - logical_line.startswith('class ') or |
940 | - logical_line.startswith('@')): |
941 | - if indent_level > 0 and blank_lines != 1: |
942 | - return 0, "E301 expected 1 blank line, found %d" % blank_lines |
943 | - if indent_level == 0 and blank_lines != 2: |
944 | - return 0, "E302 expected 2 blank lines, found %d" % blank_lines |
945 | - if blank_lines > 2: |
946 | - return 0, "E303 too many blank lines (%d)" % blank_lines |
947 | + if max_blank_lines: |
948 | + return 0, "E304 blank lines found after function decorator" |
949 | + elif max_blank_lines > 2 or (indent_level and max_blank_lines == 2): |
950 | + return 0, "E303 too many blank lines (%d)" % max_blank_lines |
951 | + elif (logical_line.startswith('def ') or |
952 | + logical_line.startswith('class ') or |
953 | + logical_line.startswith('@')): |
954 | + if indent_level: |
955 | + if not (max_blank_lines or previous_indent_level < indent_level or |
956 | + DOCSTRING_REGEX.match(previous_logical)): |
957 | + return 0, "E301 expected 1 blank line, found 0" |
958 | + elif max_blank_lines != 2: |
959 | + return 0, "E302 expected 2 blank lines, found %d" % max_blank_lines |
960 | |
961 | |
962 | def extraneous_whitespace(logical_line): |
963 | @@ -227,45 +308,75 @@ |
964 | - Immediately inside parentheses, brackets or braces. |
965 | |
966 | - Immediately before a comma, semicolon, or colon. |
967 | + |
968 | + Okay: spam(ham[1], {eggs: 2}) |
969 | + E201: spam( ham[1], {eggs: 2}) |
970 | + E201: spam(ham[ 1], {eggs: 2}) |
971 | + E201: spam(ham[1], { eggs: 2}) |
972 | + E202: spam(ham[1], {eggs: 2} ) |
973 | + E202: spam(ham[1 ], {eggs: 2}) |
974 | + E202: spam(ham[1], {eggs: 2 }) |
975 | + |
976 | + E203: if x == 4: print x, y; x, y = y , x |
977 | + E203: if x == 4: print x, y ; x, y = y, x |
978 | + E203: if x == 4 : print x, y; x, y = y, x |
979 | """ |
980 | line = logical_line |
981 | - for char in '([{': |
982 | - found = line.find(char + ' ') |
983 | - if found > -1: |
984 | + for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): |
985 | + text = match.group() |
986 | + char = text.strip() |
987 | + found = match.start() |
988 | + if text == char + ' ' and char in '([{': |
989 | return found + 1, "E201 whitespace after '%s'" % char |
990 | - for char in '}])': |
991 | - found = line.find(' ' + char) |
992 | - if found > -1 and line[found - 1] != ',': |
993 | - return found, "E202 whitespace before '%s'" % char |
994 | - for char in ',;:': |
995 | - found = line.find(' ' + char) |
996 | - if found > -1: |
997 | - return found, "E203 whitespace before '%s'" % char |
998 | + if text == ' ' + char and line[found - 1] != ',': |
999 | + if char in '}])': |
1000 | + return found, "E202 whitespace before '%s'" % char |
1001 | + if char in ',;:': |
1002 | + return found, "E203 whitespace before '%s'" % char |
1003 | |
1004 | |
1005 | def missing_whitespace(logical_line): |
1006 | """ |
1007 | JCR: Each comma, semicolon or colon should be followed by whitespace. |
1008 | + |
1009 | + Okay: [a, b] |
1010 | + Okay: (3,) |
1011 | + Okay: a[1:4] |
1012 | + Okay: a[:4] |
1013 | + Okay: a[1:] |
1014 | + Okay: a[1:4:2] |
1015 | + E231: ['a','b'] |
1016 | + E231: foo(bar,baz) |
1017 | """ |
1018 | line = logical_line |
1019 | for index in range(len(line) - 1): |
1020 | char = line[index] |
1021 | - if char in ',;:' and line[index + 1] != ' ': |
1022 | + if char in ',;:' and line[index + 1] not in WHITESPACE: |
1023 | before = line[:index] |
1024 | if char == ':' and before.count('[') > before.count(']'): |
1025 | - continue # Slice syntax, no space required |
1026 | + continue # Slice syntax, no space required |
1027 | if char == ',' and line[index + 1] == ')': |
1028 | - continue # Singleton tuple syntax, no space required |
1029 | + continue # Allow tuple with only one element: (3,) |
1030 | return index, "E231 missing whitespace after '%s'" % char |
1031 | |
1032 | |
1033 | def indentation(logical_line, previous_logical, indent_char, |
1034 | indent_level, previous_indent_level): |
1035 | - """ |
1036 | + r""" |
1037 | Use 4 spaces per indentation level. |
1038 | |
1039 | For really old code that you don't want to mess up, you can continue to |
1040 | use 8-space tabs. |
1041 | + |
1042 | + Okay: a = 1 |
1043 | + Okay: if a == 0:\n a = 1 |
1044 | + E111: a = 1 |
1045 | + |
1046 | + Okay: for item in items:\n pass |
1047 | + E112: for item in items:\npass |
1048 | + |
1049 | + Okay: a = 1\nb = 2 |
1050 | + E113: a = 1\n b = 2 |
1051 | """ |
1052 | if indent_char == ' ' and indent_level % 4: |
1053 | return 0, "E111 indentation is not a multiple of four" |
1054 | @@ -285,6 +396,13 @@ |
1055 | |
1056 | - Immediately before the open parenthesis that starts an indexing or |
1057 | slicing. |
1058 | + |
1059 | + Okay: spam(1) |
1060 | + E211: spam (1) |
1061 | + |
1062 | + Okay: dict['key'] = list[index] |
1063 | + E211: dict ['key'] = list[index] |
1064 | + E211: dict['key'] = list [index] |
1065 | """ |
1066 | prev_type = tokens[0][0] |
1067 | prev_text = tokens[0][1] |
1068 | @@ -294,9 +412,11 @@ |
1069 | if (token_type == tokenize.OP and |
1070 | text in '([' and |
1071 | start != prev_end and |
1072 | - prev_type == tokenize.NAME and |
1073 | + (prev_type == tokenize.NAME or prev_text in '}])') and |
1074 | + # Syntax "class A (B):" is allowed, but avoid it |
1075 | (index < 2 or tokens[index - 2][1] != 'class') and |
1076 | - (not iskeyword(prev_text))): |
1077 | + # Allow "return (a.foo for a in range(5))" |
1078 | + (not keyword.iskeyword(prev_text))): |
1079 | return prev_end, "E211 whitespace before '%s'" % text |
1080 | prev_type = token_type |
1081 | prev_text = text |
1082 | @@ -309,21 +429,97 @@ |
1083 | |
1084 | - More than one space around an assignment (or other) operator to |
1085 | align it with another. |
1086 | - """ |
1087 | - line = logical_line |
1088 | - for operator in operators: |
1089 | - found = line.find(' ' + operator) |
1090 | - if found > -1: |
1091 | - return found, "E221 multiple spaces before operator" |
1092 | - found = line.find(operator + ' ') |
1093 | - if found > -1: |
1094 | - return found, "E222 multiple spaces after operator" |
1095 | - found = line.find('\t' + operator) |
1096 | - if found > -1: |
1097 | - return found, "E223 tab before operator" |
1098 | - found = line.find(operator + '\t') |
1099 | - if found > -1: |
1100 | - return found, "E224 tab after operator" |
1101 | + |
1102 | + Okay: a = 12 + 3 |
1103 | + E221: a = 4 + 5 |
1104 | + E222: a = 4 + 5 |
1105 | + E223: a = 4\t+ 5 |
1106 | + E224: a = 4 +\t5 |
1107 | + """ |
1108 | + for match in WHITESPACE_AROUND_OPERATOR_REGEX.finditer(logical_line): |
1109 | + before, whitespace, after = match.groups() |
1110 | + tab = whitespace == '\t' |
1111 | + offset = match.start(2) |
1112 | + if before in OPERATORS: |
1113 | + return offset, (tab and "E224 tab after operator" or |
1114 | + "E222 multiple spaces after operator") |
1115 | + elif after in OPERATORS: |
1116 | + return offset, (tab and "E223 tab before operator" or |
1117 | + "E221 multiple spaces before operator") |
1118 | + |
1119 | + |
1120 | +def missing_whitespace_around_operator(logical_line, tokens): |
1121 | + r""" |
1122 | + - Always surround these binary operators with a single space on |
1123 | + either side: assignment (=), augmented assignment (+=, -= etc.), |
1124 | + comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), |
1125 | + Booleans (and, or, not). |
1126 | + |
1127 | + - Use spaces around arithmetic operators. |
1128 | + |
1129 | + Okay: i = i + 1 |
1130 | + Okay: submitted += 1 |
1131 | + Okay: x = x * 2 - 1 |
1132 | + Okay: hypot2 = x * x + y * y |
1133 | + Okay: c = (a + b) * (a - b) |
1134 | + Okay: foo(bar, key='word', *args, **kwargs) |
1135 | + Okay: baz(**kwargs) |
1136 | + Okay: negative = -1 |
1137 | + Okay: spam(-1) |
1138 | + Okay: alpha[:-i] |
1139 | + Okay: if not -5 < x < +5:\n pass |
1140 | + Okay: lambda *args, **kw: (args, kw) |
1141 | + |
1142 | + E225: i=i+1 |
1143 | + E225: submitted +=1 |
1144 | + E225: x = x*2 - 1 |
1145 | + E225: hypot2 = x*x + y*y |
1146 | + E225: c = (a+b) * (a-b) |
1147 | + E225: c = alpha -4 |
1148 | + E225: z = x **y |
1149 | + """ |
1150 | + parens = 0 |
1151 | + need_space = False |
1152 | + prev_type = tokenize.OP |
1153 | + prev_text = prev_end = None |
1154 | + for token_type, text, start, end, line in tokens: |
1155 | + if token_type in (tokenize.NL, tokenize.NEWLINE, tokenize.ERRORTOKEN): |
1156 | + # ERRORTOKEN is triggered by backticks in Python 3000 |
1157 | + continue |
1158 | + if text in ('(', 'lambda'): |
1159 | + parens += 1 |
1160 | + elif text == ')': |
1161 | + parens -= 1 |
1162 | + if need_space: |
1163 | + if start != prev_end: |
1164 | + need_space = False |
1165 | + elif text == '>' and prev_text == '<': |
1166 | + # Tolerate the "<>" operator, even if running Python 3 |
1167 | + pass |
1168 | + else: |
1169 | + return prev_end, "E225 missing whitespace around operator" |
1170 | + elif token_type == tokenize.OP and prev_end is not None: |
1171 | + if text == '=' and parens: |
1172 | + # Allow keyword args or defaults: foo(bar=None). |
1173 | + pass |
1174 | + elif text in BINARY_OPERATORS: |
1175 | + need_space = True |
1176 | + elif text in UNARY_OPERATORS: |
1177 | + # Allow unary operators: -123, -x, +1. |
1178 | + # Allow argument unpacking: foo(*args, **kwargs). |
1179 | + if prev_type == tokenize.OP: |
1180 | + if prev_text in '}])': |
1181 | + need_space = True |
1182 | + elif prev_type == tokenize.NAME: |
1183 | + if prev_text not in E225NOT_KEYWORDS: |
1184 | + need_space = True |
1185 | + else: |
1186 | + need_space = True |
1187 | + if need_space and start == prev_end: |
1188 | + return prev_end, "E225 missing whitespace around operator" |
1189 | + prev_type = token_type |
1190 | + prev_text = text |
1191 | + prev_end = end |
1192 | |
1193 | |
1194 | def whitespace_around_comma(logical_line): |
1195 | @@ -334,6 +530,11 @@ |
1196 | align it with another. |
1197 | |
1198 | JCR: This should also be applied around comma etc. |
1199 | + Note: these checks are disabled by default |
1200 | + |
1201 | + Okay: a = (1, 2) |
1202 | + E241: a = (1, 2) |
1203 | + E242: a = (1,\t2) |
1204 | """ |
1205 | line = logical_line |
1206 | for separator in ',;:': |
1207 | @@ -345,9 +546,77 @@ |
1208 | return found + 1, "E242 tab after '%s'" % separator |
1209 | |
1210 | |
1211 | +def whitespace_around_named_parameter_equals(logical_line): |
1212 | + """ |
1213 | + Don't use spaces around the '=' sign when used to indicate a |
1214 | + keyword argument or a default parameter value. |
1215 | + |
1216 | + Okay: def complex(real, imag=0.0): |
1217 | + Okay: return magic(r=real, i=imag) |
1218 | + Okay: boolean(a == b) |
1219 | + Okay: boolean(a != b) |
1220 | + Okay: boolean(a <= b) |
1221 | + Okay: boolean(a >= b) |
1222 | + |
1223 | + E251: def complex(real, imag = 0.0): |
1224 | + E251: return magic(r = real, i = imag) |
1225 | + """ |
1226 | + parens = 0 |
1227 | + for match in WHITESPACE_AROUND_NAMED_PARAMETER_REGEX.finditer( |
1228 | + logical_line): |
1229 | + text = match.group() |
1230 | + if parens and len(text) == 3: |
1231 | + issue = "E251 no spaces around keyword / parameter equals" |
1232 | + return match.start(), issue |
1233 | + if text == '(': |
1234 | + parens += 1 |
1235 | + elif text == ')': |
1236 | + parens -= 1 |
1237 | + |
1238 | + |
1239 | +def whitespace_before_inline_comment(logical_line, tokens): |
1240 | + """ |
1241 | + Separate inline comments by at least two spaces. |
1242 | + |
1243 | + An inline comment is a comment on the same line as a statement. Inline |
1244 | + comments should be separated by at least two spaces from the statement. |
1245 | + They should start with a # and a single space. |
1246 | + |
1247 | + Okay: x = x + 1 # Increment x |
1248 | + Okay: x = x + 1 # Increment x |
1249 | + E261: x = x + 1 # Increment x |
1250 | + E262: x = x + 1 #Increment x |
1251 | + E262: x = x + 1 # Increment x |
1252 | + """ |
1253 | + prev_end = (0, 0) |
1254 | + for token_type, text, start, end, line in tokens: |
1255 | + if token_type == tokenize.NL: |
1256 | + continue |
1257 | + if token_type == tokenize.COMMENT: |
1258 | + if not line[:start[1]].strip(): |
1259 | + continue |
1260 | + if prev_end[0] == start[0] and start[1] < prev_end[1] + 2: |
1261 | + return (prev_end, |
1262 | + "E261 at least two spaces before inline comment") |
1263 | + if (len(text) > 1 and text.startswith('# ') |
1264 | + or not text.startswith('# ')): |
1265 | + return start, "E262 inline comment should start with '# '" |
1266 | + else: |
1267 | + prev_end = end |
1268 | + |
1269 | + |
1270 | def imports_on_separate_lines(logical_line): |
1271 | - """ |
1272 | + r""" |
1273 | Imports should usually be on separate lines. |
1274 | + |
1275 | + Okay: import os\nimport sys |
1276 | + E401: import sys, os |
1277 | + |
1278 | + Okay: from subprocess import Popen, PIPE |
1279 | + Okay: from myclas import MyClass |
1280 | + Okay: from foo.bar.yourclass import YourClass |
1281 | + Okay: import myclass |
1282 | + Okay: import foo.bar.yourclass |
1283 | """ |
1284 | line = logical_line |
1285 | if line.startswith('import '): |
1286 | @@ -357,17 +626,37 @@ |
1287 | |
1288 | |
1289 | def compound_statements(logical_line): |
1290 | - """ |
1291 | + r""" |
1292 | Compound statements (multiple statements on the same line) are |
1293 | generally discouraged. |
1294 | + |
1295 | + While sometimes it's okay to put an if/for/while with a small body |
1296 | + on the same line, never do this for multi-clause statements. Also |
1297 | + avoid folding such long lines! |
1298 | + |
1299 | + Okay: if foo == 'blah':\n do_blah_thing() |
1300 | + Okay: do_one() |
1301 | + Okay: do_two() |
1302 | + Okay: do_three() |
1303 | + |
1304 | + E701: if foo == 'blah': do_blah_thing() |
1305 | + E701: for x in lst: total += x |
1306 | + E701: while t < 10: t = delay() |
1307 | + E701: if foo == 'blah': do_blah_thing() |
1308 | + E701: else: do_non_blah_thing() |
1309 | + E701: try: something() |
1310 | + E701: finally: cleanup() |
1311 | + E701: if foo == 'blah': one(); two(); three() |
1312 | + |
1313 | + E702: do_one(); do_two(); do_three() |
1314 | """ |
1315 | line = logical_line |
1316 | found = line.find(':') |
1317 | if -1 < found < len(line) - 1: |
1318 | before = line[:found] |
1319 | - if (before.count('{') <= before.count('}') and # {'a': 1} (dict) |
1320 | - before.count('[') <= before.count(']') and # [1:2] (slice) |
1321 | - not re.search(r'\blambda\b', before)): # lambda x: x |
1322 | + if (before.count('{') <= before.count('}') and # {'a': 1} (dict) |
1323 | + before.count('[') <= before.count(']') and # [1:2] (slice) |
1324 | + not re.search(r'\blambda\b', before)): # lambda x: x |
1325 | return found, "E701 multiple statements on one line (colon)" |
1326 | found = line.find(';') |
1327 | if -1 < found: |
1328 | @@ -397,16 +686,49 @@ |
1329 | continuation characters thanks to the containing parentheses. The older |
1330 | form will be removed in Python 3000. |
1331 | """ |
1332 | - match = raise_comma_match(logical_line) |
1333 | + match = RAISE_COMMA_REGEX.match(logical_line) |
1334 | if match: |
1335 | return match.start(1), "W602 deprecated form of raising exception" |
1336 | |
1337 | |
1338 | +def python_3000_not_equal(logical_line): |
1339 | + """ |
1340 | + != can also be written <>, but this is an obsolete usage kept for |
1341 | + backwards compatibility only. New code should always use !=. |
1342 | + The older syntax is removed in Python 3000. |
1343 | + """ |
1344 | + pos = logical_line.find('<>') |
1345 | + if pos > -1: |
1346 | + return pos, "W603 '<>' is deprecated, use '!='" |
1347 | + |
1348 | + |
1349 | +def python_3000_backticks(logical_line): |
1350 | + """ |
1351 | + Backticks are removed in Python 3000. |
1352 | + Use repr() instead. |
1353 | + """ |
1354 | + pos = logical_line.find('`') |
1355 | + if pos > -1: |
1356 | + return pos, "W604 backticks are deprecated, use 'repr()'" |
1357 | + |
1358 | + |
1359 | ############################################################################## |
1360 | # Helper functions |
1361 | ############################################################################## |
1362 | |
1363 | |
1364 | +if '' == ''.encode(): |
1365 | + # Python 2: implicit encoding. |
1366 | + def readlines(filename): |
1367 | + return open(filename).readlines() |
1368 | +else: |
1369 | + # Python 3: decode to latin-1. |
1370 | + # This function is lazy, it does not read the encoding declaration. |
1371 | + # XXX: use tokenize.detect_encoding() |
1372 | + def readlines(filename): |
1373 | + return open(filename, encoding='latin-1').readlines() |
1374 | + |
1375 | + |
1376 | def expand_indent(line): |
1377 | """ |
1378 | Return the amount of indentation. |
1379 | @@ -426,7 +748,7 @@ |
1380 | result = 0 |
1381 | for char in line: |
1382 | if char == '\t': |
1383 | - result = result / 8 * 8 + 8 |
1384 | + result = result // 8 * 8 + 8 |
1385 | elif char == ' ': |
1386 | result += 1 |
1387 | else: |
1388 | @@ -434,34 +756,6 @@ |
1389 | return result |
1390 | |
1391 | |
1392 | -############################################################################## |
1393 | -# Framework to run all checks |
1394 | -############################################################################## |
1395 | - |
1396 | - |
1397 | -def message(text): |
1398 | - """Print a message.""" |
1399 | - # print >> sys.stderr, options.prog + ': ' + text |
1400 | - # print >> sys.stderr, text |
1401 | - print text |
1402 | - |
1403 | - |
1404 | -def find_checks(argument_name): |
1405 | - """ |
1406 | - Find all globally visible functions where the first argument name |
1407 | - starts with argument_name. |
1408 | - """ |
1409 | - checks = [] |
1410 | - function_type = type(find_checks) |
1411 | - for name, function in globals().iteritems(): |
1412 | - if type(function) is function_type: |
1413 | - args = inspect.getargspec(function)[0] |
1414 | - if len(args) >= 1 and args[0].startswith(argument_name): |
1415 | - checks.append((name, function, args)) |
1416 | - checks.sort() |
1417 | - return checks |
1418 | - |
1419 | - |
1420 | def mute_string(text): |
1421 | """ |
1422 | Replace contents with 'xxx' to prevent syntax matching. |
1423 | @@ -487,18 +781,53 @@ |
1424 | return text[:start] + 'x' * (end - start) + text[end:] |
1425 | |
1426 | |
1427 | -class Checker: |
1428 | +def message(text): |
1429 | + """Print a message.""" |
1430 | + # print >> sys.stderr, options.prog + ': ' + text |
1431 | + # print >> sys.stderr, text |
1432 | + print(text) |
1433 | + |
1434 | + |
1435 | +############################################################################## |
1436 | +# Framework to run all checks |
1437 | +############################################################################## |
1438 | + |
1439 | + |
1440 | +def find_checks(argument_name): |
1441 | + """ |
1442 | + Find all globally visible functions where the first argument name |
1443 | + starts with argument_name. |
1444 | + """ |
1445 | + checks = [] |
1446 | + for name, function in globals().items(): |
1447 | + if not inspect.isfunction(function): |
1448 | + continue |
1449 | + args = inspect.getargspec(function)[0] |
1450 | + if args and args[0].startswith(argument_name): |
1451 | + codes = ERRORCODE_REGEX.findall(inspect.getdoc(function) or '') |
1452 | + for code in codes or ['']: |
1453 | + if not code or not ignore_code(code): |
1454 | + checks.append((name, function, args)) |
1455 | + break |
1456 | + checks.sort() |
1457 | + return checks |
1458 | + |
1459 | + |
1460 | +class Checker(object): |
1461 | """ |
1462 | Load a Python source file, tokenize it, check coding style. |
1463 | """ |
1464 | |
1465 | - def __init__(self, filename): |
1466 | + def __init__(self, filename, lines=None): |
1467 | self.filename = filename |
1468 | - self.lines = file(filename).readlines() |
1469 | - self.physical_checks = find_checks('physical_line') |
1470 | - self.logical_checks = find_checks('logical_line') |
1471 | - options.counters['physical lines'] = \ |
1472 | - options.counters.get('physical lines', 0) + len(self.lines) |
1473 | + if filename is None: |
1474 | + self.filename = 'stdin' |
1475 | + self.lines = lines or [] |
1476 | + elif lines is None: |
1477 | + self.lines = readlines(filename) |
1478 | + else: |
1479 | + self.lines = lines |
1480 | + options.counters['physical lines'] += len(self.lines) |
1481 | |
1482 | def readline(self): |
1483 | """ |
1484 | @@ -535,7 +864,7 @@ |
1485 | self.physical_line = line |
1486 | if self.indent_char is None and len(line) and line[0] in ' \t': |
1487 | self.indent_char = line[0] |
1488 | - for name, check, argument_names in self.physical_checks: |
1489 | + for name, check, argument_names in options.physical_checks: |
1490 | result = self.run_check(check, argument_names) |
1491 | if result is not None: |
1492 | offset, text = result |
1493 | @@ -551,20 +880,20 @@ |
1494 | previous = None |
1495 | for token in self.tokens: |
1496 | token_type, text = token[0:2] |
1497 | - if token_type in (tokenize.COMMENT, tokenize.NL, |
1498 | - tokenize.INDENT, tokenize.DEDENT, |
1499 | - tokenize.NEWLINE): |
1500 | + if token_type in SKIP_TOKENS: |
1501 | continue |
1502 | if token_type == tokenize.STRING: |
1503 | text = mute_string(text) |
1504 | if previous: |
1505 | end_line, end = previous[3] |
1506 | start_line, start = token[2] |
1507 | - if end_line != start_line: # different row |
1508 | - if self.lines[end_line - 1][end - 1] not in '{[(': |
1509 | + if end_line != start_line: # different row |
1510 | + prev_text = self.lines[end_line - 1][end - 1] |
1511 | + if prev_text == ',' or (prev_text not in '{[(' |
1512 | + and text not in '}])'): |
1513 | logical.append(' ') |
1514 | length += 1 |
1515 | - elif end != start: # different column |
1516 | + elif end != start: # different column |
1517 | fill = self.lines[end_line - 1][end:start] |
1518 | logical.append(fill) |
1519 | length += len(fill) |
1520 | @@ -580,22 +909,21 @@ |
1521 | """ |
1522 | Build a line from tokens and run all logical checks on it. |
1523 | """ |
1524 | - options.counters['logical lines'] = \ |
1525 | - options.counters.get('logical lines', 0) + 1 |
1526 | + options.counters['logical lines'] += 1 |
1527 | self.build_tokens_line() |
1528 | first_line = self.lines[self.mapping[0][1][2][0] - 1] |
1529 | indent = first_line[:self.mapping[0][1][2][1]] |
1530 | self.previous_indent_level = self.indent_level |
1531 | self.indent_level = expand_indent(indent) |
1532 | if options.verbose >= 2: |
1533 | - print self.logical_line[:80].rstrip() |
1534 | - for name, check, argument_names in self.logical_checks: |
1535 | - if options.verbose >= 3: |
1536 | - print ' ', name |
1537 | + print(self.logical_line[:80].rstrip()) |
1538 | + for name, check, argument_names in options.logical_checks: |
1539 | + if options.verbose >= 4: |
1540 | + print(' ' + name) |
1541 | result = self.run_check(check, argument_names) |
1542 | if result is not None: |
1543 | offset, text = result |
1544 | - if type(offset) is tuple: |
1545 | + if isinstance(offset, tuple): |
1546 | original_number, original_offset = offset |
1547 | else: |
1548 | for token_offset, token in self.mapping: |
1549 | @@ -607,20 +935,29 @@ |
1550 | text, check) |
1551 | self.previous_logical = self.logical_line |
1552 | |
1553 | - def check_all(self): |
1554 | + def check_all(self, expected=None, line_offset=0): |
1555 | """ |
1556 | Run all checks on the input file. |
1557 | """ |
1558 | + self.expected = expected or () |
1559 | + self.line_offset = line_offset |
1560 | + self.line_number = 0 |
1561 | self.file_errors = 0 |
1562 | - self.line_number = 0 |
1563 | self.indent_char = None |
1564 | self.indent_level = 0 |
1565 | self.previous_logical = '' |
1566 | self.blank_lines = 0 |
1567 | + self.blank_lines_before_comment = 0 |
1568 | self.tokens = [] |
1569 | parens = 0 |
1570 | for token in tokenize.generate_tokens(self.readline_check_physical): |
1571 | - # print tokenize.tok_name[token[0]], repr(token) |
1572 | + if options.verbose >= 3: |
1573 | + if token[2][0] == token[3][0]: |
1574 | + pos = '[%s:%s]' % (token[2][1] or '', token[3][1]) |
1575 | + else: |
1576 | + pos = 'l.%s' % token[3][0] |
1577 | + print('l.%s\t%s\t%s\t%r' % |
1578 | + (token[2][0], pos, tokenize.tok_name[token[0]], token[1])) |
1579 | self.tokens.append(token) |
1580 | token_type, text = token[0:2] |
1581 | if token_type == tokenize.OP and text in '([{': |
1582 | @@ -630,40 +967,49 @@ |
1583 | if token_type == tokenize.NEWLINE and not parens: |
1584 | self.check_logical() |
1585 | self.blank_lines = 0 |
1586 | + self.blank_lines_before_comment = 0 |
1587 | self.tokens = [] |
1588 | if token_type == tokenize.NL and not parens: |
1589 | - self.blank_lines += 1 |
1590 | + if len(self.tokens) <= 1: |
1591 | + # The physical line contains only this token. |
1592 | + self.blank_lines += 1 |
1593 | self.tokens = [] |
1594 | if token_type == tokenize.COMMENT: |
1595 | source_line = token[4] |
1596 | token_start = token[2][1] |
1597 | if source_line[:token_start].strip() == '': |
1598 | + self.blank_lines_before_comment = max(self.blank_lines, |
1599 | + self.blank_lines_before_comment) |
1600 | self.blank_lines = 0 |
1601 | + if text.endswith('\n') and not parens: |
1602 | + # The comment also ends a physical line. This works around |
1603 | + # Python < 2.6 behaviour, which does not generate NL after |
1604 | + # a comment which is on a line by itself. |
1605 | + self.tokens = [] |
1606 | return self.file_errors |
1607 | |
1608 | def report_error(self, line_number, offset, text, check): |
1609 | """ |
1610 | Report an error, according to options. |
1611 | """ |
1612 | + code = text[:4] |
1613 | + if ignore_code(code): |
1614 | + return |
1615 | if options.quiet == 1 and not self.file_errors: |
1616 | message(self.filename) |
1617 | + if code in options.counters: |
1618 | + options.counters[code] += 1 |
1619 | + else: |
1620 | + options.counters[code] = 1 |
1621 | + options.messages[code] = text[5:] |
1622 | + if options.quiet or code in self.expected: |
1623 | + # Don't care about expected errors or warnings |
1624 | + return |
1625 | self.file_errors += 1 |
1626 | - code = text[:4] |
1627 | - options.counters[code] = options.counters.get(code, 0) + 1 |
1628 | - options.messages[code] = text[5:] |
1629 | - if options.quiet: |
1630 | - return |
1631 | - if options.testsuite: |
1632 | - base = os.path.basename(self.filename)[:4] |
1633 | - if base == code: |
1634 | - return |
1635 | - if base[0] == 'E' and code[0] == 'W': |
1636 | - return |
1637 | - if ignore_code(code): |
1638 | - return |
1639 | if options.counters[code] == 1 or options.repeat: |
1640 | message("%s:%s:%d: %s" % |
1641 | - (self.filename, line_number, offset + 1, text)) |
1642 | + (self.filename, self.line_offset + line_number, |
1643 | + offset + 1, text)) |
1644 | if options.show_source: |
1645 | line = self.lines[line_number - 1] |
1646 | message(line.rstrip()) |
1647 | @@ -676,35 +1022,33 @@ |
1648 | """ |
1649 | Run all checks on a Python source file. |
1650 | """ |
1651 | - if excluded(filename) or not filename_match(filename): |
1652 | - return {} |
1653 | if options.verbose: |
1654 | message('checking ' + filename) |
1655 | - options.counters['files'] = options.counters.get('files', 0) + 1 |
1656 | errors = Checker(filename).check_all() |
1657 | - if options.testsuite and not errors: |
1658 | - message("%s: %s" % (filename, "no errors found")) |
1659 | - |
1660 | - |
1661 | -def input_dir(dirname): |
1662 | + |
1663 | + |
1664 | +def input_dir(dirname, runner=None): |
1665 | """ |
1666 | Check all Python source files in this directory and all subdirectories. |
1667 | """ |
1668 | dirname = dirname.rstrip('/') |
1669 | if excluded(dirname): |
1670 | return |
1671 | + if runner is None: |
1672 | + runner = input_file |
1673 | for root, dirs, files in os.walk(dirname): |
1674 | if options.verbose: |
1675 | message('directory ' + root) |
1676 | - options.counters['directories'] = \ |
1677 | - options.counters.get('directories', 0) + 1 |
1678 | + options.counters['directories'] += 1 |
1679 | dirs.sort() |
1680 | for subdir in dirs: |
1681 | if excluded(subdir): |
1682 | dirs.remove(subdir) |
1683 | files.sort() |
1684 | for filename in files: |
1685 | - input_file(os.path.join(root, filename)) |
1686 | + if filename_match(filename) and not excluded(filename): |
1687 | + options.counters['files'] += 1 |
1688 | + runner(os.path.join(root, filename)) |
1689 | |
1690 | |
1691 | def excluded(filename): |
1692 | @@ -733,12 +1077,23 @@ |
1693 | def ignore_code(code): |
1694 | """ |
1695 | Check if options.ignore contains a prefix of the error code. |
1696 | + If options.select contains a prefix of the error code, do not ignore it. |
1697 | """ |
1698 | + for select in options.select: |
1699 | + if code.startswith(select): |
1700 | + return False |
1701 | for ignore in options.ignore: |
1702 | if code.startswith(ignore): |
1703 | return True |
1704 | |
1705 | |
1706 | +def reset_counters(): |
1707 | + for key in list(options.counters.keys()): |
1708 | + if key not in BENCHMARK_KEYS: |
1709 | + del options.counters[key] |
1710 | + options.messages = {} |
1711 | + |
1712 | + |
1713 | def get_error_statistics(): |
1714 | """Get error statistics.""" |
1715 | return get_statistics("E") |
1716 | @@ -759,7 +1114,7 @@ |
1717 | prefix='E4' matches all errors that have to do with imports |
1718 | """ |
1719 | stats = [] |
1720 | - keys = options.messages.keys() |
1721 | + keys = list(options.messages.keys()) |
1722 | keys.sort() |
1723 | for key in keys: |
1724 | if key.startswith(prefix): |
1725 | @@ -768,24 +1123,131 @@ |
1726 | return stats |
1727 | |
1728 | |
1729 | +def get_count(prefix=''): |
1730 | + """Return the total count of errors and warnings.""" |
1731 | + keys = list(options.messages.keys()) |
1732 | + count = 0 |
1733 | + for key in keys: |
1734 | + if key.startswith(prefix): |
1735 | + count += options.counters[key] |
1736 | + return count |
1737 | + |
1738 | + |
1739 | def print_statistics(prefix=''): |
1740 | """Print overall statistics (number of errors and warnings).""" |
1741 | for line in get_statistics(prefix): |
1742 | - print line |
1743 | + print(line) |
1744 | |
1745 | |
1746 | def print_benchmark(elapsed): |
1747 | """ |
1748 | Print benchmark numbers. |
1749 | """ |
1750 | - print '%-7.2f %s' % (elapsed, 'seconds elapsed') |
1751 | - keys = ['directories', 'files', |
1752 | - 'logical lines', 'physical lines'] |
1753 | - for key in keys: |
1754 | - if key in options.counters: |
1755 | - print '%-7d %s per second (%d total)' % ( |
1756 | - options.counters[key] / elapsed, key, |
1757 | - options.counters[key]) |
1758 | + print('%-7.2f %s' % (elapsed, 'seconds elapsed')) |
1759 | + for key in BENCHMARK_KEYS: |
1760 | + print('%-7d %s per second (%d total)' % ( |
1761 | + options.counters[key] / elapsed, key, |
1762 | + options.counters[key])) |
1763 | + |
1764 | + |
1765 | +def run_tests(filename): |
1766 | + """ |
1767 | + Run all the tests from a file. |
1768 | + |
1769 | + A test file can provide many tests. Each test starts with a declaration. |
1770 | + This declaration is a single line starting with '#:'. |
1771 | + It declares codes of expected failures, separated by spaces or 'Okay' |
1772 | + if no failure is expected. |
1773 | + If the file does not contain such declaration, it should pass all tests. |
1774 | + If the declaration is empty, following lines are not checked, until next |
1775 | + declaration. |
1776 | + |
1777 | + Examples: |
1778 | + |
1779 | + * Only E224 and W701 are expected: #: E224 W701 |
1780 | + * Following example is conform: #: Okay |
1781 | + * Don't check these lines: #: |
1782 | + """ |
1783 | + lines = readlines(filename) + ['#:\n'] |
1784 | + line_offset = 0 |
1785 | + codes = ['Okay'] |
1786 | + testcase = [] |
1787 | + for index, line in enumerate(lines): |
1788 | + if not line.startswith('#:'): |
1789 | + if codes: |
1790 | + # Collect the lines of the test case |
1791 | + testcase.append(line) |
1792 | + continue |
1793 | + if codes and index > 0: |
1794 | + label = '%s:%s:1' % (filename, line_offset + 1) |
1795 | + codes = [c for c in codes if c != 'Okay'] |
1796 | + # Run the checker |
1797 | + errors = Checker(filename, testcase).check_all(codes, line_offset) |
1798 | + # Check if the expected errors were found |
1799 | + for code in codes: |
1800 | + if not options.counters.get(code): |
1801 | + errors += 1 |
1802 | + message('%s: error %s not found' % (label, code)) |
1803 | + if options.verbose and not errors: |
1804 | + message('%s: passed (%s)' % (label, ' '.join(codes))) |
1805 | + # Keep showing errors for multiple tests |
1806 | + reset_counters() |
1807 | + # output the real line numbers |
1808 | + line_offset = index |
1809 | + # configure the expected errors |
1810 | + codes = line.split()[1:] |
1811 | + # empty the test case buffer |
1812 | + del testcase[:] |
1813 | + |
1814 | + |
1815 | +def selftest(): |
1816 | + """ |
1817 | + Test all check functions with test cases in docstrings. |
1818 | + """ |
1819 | + count_passed = 0 |
1820 | + count_failed = 0 |
1821 | + checks = options.physical_checks + options.logical_checks |
1822 | + for name, check, argument_names in checks: |
1823 | + for line in check.__doc__.splitlines(): |
1824 | + line = line.lstrip() |
1825 | + match = SELFTEST_REGEX.match(line) |
1826 | + if match is None: |
1827 | + continue |
1828 | + code, source = match.groups() |
1829 | + checker = Checker(None) |
1830 | + for part in source.split(r'\n'): |
1831 | + part = part.replace(r'\t', '\t') |
1832 | + part = part.replace(r'\s', ' ') |
1833 | + checker.lines.append(part + '\n') |
1834 | + options.quiet = 2 |
1835 | + checker.check_all() |
1836 | + error = None |
1837 | + if code == 'Okay': |
1838 | + if len(options.counters) > len(BENCHMARK_KEYS): |
1839 | + codes = [key for key in options.counters.keys() |
1840 | + if key not in BENCHMARK_KEYS] |
1841 | + error = "incorrectly found %s" % ', '.join(codes) |
1842 | + elif not options.counters.get(code): |
1843 | + error = "failed to find %s" % code |
1844 | + # Reset the counters |
1845 | + reset_counters() |
1846 | + if not error: |
1847 | + count_passed += 1 |
1848 | + else: |
1849 | + count_failed += 1 |
1850 | + if len(checker.lines) == 1: |
1851 | + print("pep8.py: %s: %s" % |
1852 | + (error, checker.lines[0].rstrip())) |
1853 | + else: |
1854 | + print("pep8.py: %s:" % error) |
1855 | + for line in checker.lines: |
1856 | + print(line.rstrip()) |
1857 | + if options.verbose: |
1858 | + print("%d passed and %d failed." % (count_passed, count_failed)) |
1859 | + if count_failed: |
1860 | + print("Test failed.") |
1861 | + else: |
1862 | + print("Test passed.") |
1863 | |
1864 | |
1865 | def process_options(arglist=None): |
1866 | @@ -793,26 +1255,36 @@ |
1867 | Process options passed either via arglist or via command line args. |
1868 | """ |
1869 | global options, args |
1870 | - usage = "%prog [options] input ..." |
1871 | - parser = OptionParser(usage) |
1872 | + parser = OptionParser(version=__version__, |
1873 | + usage="%prog [options] input ...") |
1874 | parser.add_option('-v', '--verbose', default=0, action='count', |
1875 | help="print status messages, or debug with -vv") |
1876 | parser.add_option('-q', '--quiet', default=0, action='count', |
1877 | help="report only file names, or nothing with -qq") |
1878 | - parser.add_option('--exclude', metavar='patterns', default=default_exclude, |
1879 | - help="skip matches (default %s)" % default_exclude) |
1880 | - parser.add_option('--filename', metavar='patterns', |
1881 | - help="only check matching files (e.g. *.py)") |
1882 | + parser.add_option('-r', '--repeat', action='store_true', |
1883 | + help="show all occurrences of the same error") |
1884 | + parser.add_option('--exclude', metavar='patterns', default=DEFAULT_EXCLUDE, |
1885 | + help="exclude files or directories which match these " |
1886 | + "comma separated patterns (default: %s)" % |
1887 | + DEFAULT_EXCLUDE) |
1888 | + parser.add_option('--filename', metavar='patterns', default='*.py', |
1889 | + help="when parsing directories, only check filenames " |
1890 | + "matching these comma separated patterns (default: " |
1891 | + "*.py)") |
1892 | + parser.add_option('--select', metavar='errors', default='', |
1893 | + help="select errors and warnings (e.g. E,W6)") |
1894 | parser.add_option('--ignore', metavar='errors', default='', |
1895 | help="skip errors and warnings (e.g. E4,W)") |
1896 | - parser.add_option('--repeat', action='store_true', |
1897 | - help="show all occurrences of the same error") |
1898 | parser.add_option('--show-source', action='store_true', |
1899 | help="show source code for each error") |
1900 | parser.add_option('--show-pep8', action='store_true', |
1901 | help="show text of PEP 8 for each error") |
1902 | parser.add_option('--statistics', action='store_true', |
1903 | help="count errors and warnings") |
1904 | + parser.add_option('--count', action='store_true', |
1905 | + help="print total number of errors and warnings " |
1906 | + "to standard error and set exit code to 1 if " |
1907 | + "total is not null") |
1908 | parser.add_option('--benchmark', action='store_true', |
1909 | help="measure processing speed") |
1910 | parser.add_option('--testsuite', metavar='dir', |
1911 | @@ -822,7 +1294,7 @@ |
1912 | options, args = parser.parse_args(arglist) |
1913 | if options.testsuite: |
1914 | args.append(options.testsuite) |
1915 | - if len(args) == 0: |
1916 | + if not args and not options.doctest: |
1917 | parser.error('input not specified') |
1918 | options.prog = os.path.basename(sys.argv[0]) |
1919 | options.exclude = options.exclude.split(',') |
1920 | @@ -830,13 +1302,25 @@ |
1921 | options.exclude[index] = options.exclude[index].rstrip('/') |
1922 | if options.filename: |
1923 | options.filename = options.filename.split(',') |
1924 | + if options.select: |
1925 | + options.select = options.select.split(',') |
1926 | + else: |
1927 | + options.select = [] |
1928 | if options.ignore: |
1929 | options.ignore = options.ignore.split(',') |
1930 | - else: |
1931 | + elif options.select: |
1932 | + # Ignore all checks which are not explicitly selected |
1933 | + options.ignore = [''] |
1934 | + elif options.testsuite or options.doctest: |
1935 | + # For doctest and testsuite, all checks are required |
1936 | options.ignore = [] |
1937 | - options.counters = {} |
1938 | + else: |
1939 | + # The default choice: ignore controversial checks |
1940 | + options.ignore = DEFAULT_IGNORE.split(',') |
1941 | + options.physical_checks = find_checks('physical_line') |
1942 | + options.logical_checks = find_checks('logical_line') |
1943 | + options.counters = dict.fromkeys(BENCHMARK_KEYS, 0) |
1944 | options.messages = {} |
1945 | - |
1946 | return options, args |
1947 | |
1948 | |
1949 | @@ -847,18 +1331,29 @@ |
1950 | options, args = process_options() |
1951 | if options.doctest: |
1952 | import doctest |
1953 | - return doctest.testmod() |
1954 | + doctest.testmod(verbose=options.verbose) |
1955 | + selftest() |
1956 | + if options.testsuite: |
1957 | + runner = run_tests |
1958 | + else: |
1959 | + runner = input_file |
1960 | start_time = time.time() |
1961 | for path in args: |
1962 | if os.path.isdir(path): |
1963 | - input_dir(path) |
1964 | - else: |
1965 | - input_file(path) |
1966 | + input_dir(path, runner=runner) |
1967 | + elif not excluded(path): |
1968 | + options.counters['files'] += 1 |
1969 | + runner(path) |
1970 | elapsed = time.time() - start_time |
1971 | if options.statistics: |
1972 | print_statistics() |
1973 | if options.benchmark: |
1974 | print_benchmark(elapsed) |
1975 | + count = get_count() |
1976 | + if count: |
1977 | + if options.count: |
1978 | + sys.stderr.write(str(count) + '\n') |
1979 | + sys.exit(1) |
1980 | |
1981 | |
1982 | if __name__ == '__main__': |
1983 | |
1984 | === modified file 'pocketlint/formatcheck.py' |
1985 | --- pocketlint/formatcheck.py 2011-04-15 13:37:44 +0000 |
1986 | +++ pocketlint/formatcheck.py 2011-10-14 21:24:26 +0000 |
1987 | @@ -3,10 +3,15 @@ |
1988 | # This software is licensed under the MIT license (see the file COPYING). |
1989 | """Check for syntax and style problems.""" |
1990 | |
1991 | -from __future__ import with_statement |
1992 | - |
1993 | __metaclass__ = type |
1994 | |
1995 | + |
1996 | +__all__ = [ |
1997 | + 'Reporter', |
1998 | + 'UniversalChecker', |
1999 | + ] |
2000 | + |
2001 | + |
2002 | import compiler |
2003 | import htmlentitydefs |
2004 | import logging |
2005 | @@ -31,26 +36,39 @@ |
2006 | from formatdoctest import DoctestReviewer |
2007 | |
2008 | import contrib.pep8 as pep8 |
2009 | -from contrib.pyflakes.checker import Checker |
2010 | +from contrib.cssccc import CSSCodingConventionChecker |
2011 | +from contrib.pyflakes.checker import Checker as PyFlakesChecker |
2012 | try: |
2013 | import cssutils |
2014 | HAS_CSSUTILS = True |
2015 | except ImportError: |
2016 | HAS_CSSUTILS = False |
2017 | |
2018 | -# Javascript checking is available if spider money's js is available. |
2019 | -js = subprocess.Popen( |
2020 | - ['which', 'js'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
2021 | -js_exec, ignore = js.communicate() |
2022 | -if js.returncode != 0: |
2023 | - JS = None |
2024 | -else: |
2025 | - JS = js_exec.strip() |
2026 | - |
2027 | -__all__ = [ |
2028 | - 'Reporter', |
2029 | - 'UniversalChecker', |
2030 | - ] |
2031 | + |
2032 | +def find_exec(names): |
2033 | + """Return the name of a GI enabled JS interpreter.""" |
2034 | + for name in names: |
2035 | + js = subprocess.Popen( |
2036 | + ['which', name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
2037 | + js_exec, ignore = js.communicate() |
2038 | + if js.returncode == 0: |
2039 | + return js_exec.strip() |
2040 | + |
2041 | + |
2042 | +JS = find_exec(['gjs', 'seed']) |
2043 | + |
2044 | + |
2045 | +class PocketLintPyFlakesChecker(PyFlakesChecker): |
2046 | + '''PocketLint checker for pyflakes. |
2047 | + |
2048 | + This is here to work around some of the pyflakes problems. |
2049 | + ''' |
2050 | + |
2051 | + def NAME(self, node): |
2052 | + '''Locate name. Ignore WindowsErrors.''' |
2053 | + if node.name == 'WindowsError': |
2054 | + return |
2055 | + return super(PocketLintPyFlakesChecker, self).NAME(node) |
2056 | |
2057 | |
2058 | class Reporter: |
2059 | @@ -65,10 +83,12 @@ |
2060 | self.treestore = self.file_lines_view.get_model() |
2061 | self.piter = None |
2062 | self._last_file_name = None |
2063 | + self.call_count = 0 |
2064 | |
2065 | def __call__(self, line_no, message, icon=None, |
2066 | base_dir=None, file_name=None): |
2067 | """Report a message.""" |
2068 | + self.call_count += 1 |
2069 | if self.report_type == self.FILE_LINES: |
2070 | self._message_file_lines( |
2071 | line_no, message, icon=icon, |
2072 | @@ -117,6 +137,7 @@ |
2073 | ZCML = object() |
2074 | DOCBOOK = object() |
2075 | LOG = object() |
2076 | + SQL = object() |
2077 | |
2078 | XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK) |
2079 | |
2080 | @@ -130,6 +151,7 @@ |
2081 | 'text/css': CSS, |
2082 | 'text/html': HTML, |
2083 | 'text/plain': TEXT, |
2084 | + 'text/x-sql': SQL, |
2085 | 'text/x-log': LOG, |
2086 | 'application/javascript': JAVASCRIPT, |
2087 | 'application/xml': XML, |
2088 | @@ -285,6 +307,20 @@ |
2089 | self.check_conflicts(line_no, line) |
2090 | |
2091 | |
2092 | +class SQLChecker(BaseChecker, AnyTextMixin): |
2093 | + """Verify SQL style.""" |
2094 | + |
2095 | + def check(self): |
2096 | + """Call each line_method for each line in text.""" |
2097 | + # Consider http://code.google.com/p/python-sqlparse/ to verify |
2098 | + # keywords and reformatting. |
2099 | + for line_no, line in enumerate(self.text.splitlines()): |
2100 | + line_no += 1 |
2101 | + self.check_trailing_whitespace(line_no, line) |
2102 | + self.check_tab(line_no, line) |
2103 | + self.check_conflicts(line_no, line) |
2104 | + |
2105 | + |
2106 | class XMLChecker(BaseChecker, AnyTextMixin): |
2107 | """Check XML documents.""" |
2108 | |
2109 | @@ -312,7 +348,7 @@ |
2110 | start, end = match.span(0) |
2111 | text = text[:start] + self.xhtml_doctype + text[end:] |
2112 | try: |
2113 | - root = ElementTree.parse(StringIO(text), parser) |
2114 | + ElementTree.parse(StringIO(text), parser) |
2115 | except (ExpatError, ParseError), error: |
2116 | if hasattr(error, 'code'): |
2117 | error_message = ErrorString(error.code) |
2118 | @@ -326,7 +362,7 @@ |
2119 | error_lineno = 0 |
2120 | else: |
2121 | error_message, location = str(error).rsplit(':') |
2122 | - error_lineno = int(location.split(',')[0].split()[1])- offset |
2123 | + error_lineno = int(location.split(',')[0].split()[1]) - offset |
2124 | self.message(error_lineno, error_message, icon='error') |
2125 | self.check_text() |
2126 | |
2127 | @@ -378,7 +414,18 @@ |
2128 | |
2129 | def check(self): |
2130 | """Check the syntax of the CSS code.""" |
2131 | - if self.text == '' or not HAS_CSSUTILS: |
2132 | + if self.text == '': |
2133 | + return |
2134 | + |
2135 | + self.check_cssutils() |
2136 | + self.check_text() |
2137 | + # CSS coding conventoins checks should go last since they rely |
2138 | + # on previous checks. |
2139 | + self.check_css_coding_conventions() |
2140 | + |
2141 | + def check_cssutils(self): |
2142 | + """Check the CSS code by parsing it using CSSUtils module.""" |
2143 | + if not HAS_CSSUTILS: |
2144 | return |
2145 | handler = CSSReporterHandler(self) |
2146 | log = logging.getLogger('pocket-lint') |
2147 | @@ -387,7 +434,6 @@ |
2148 | log=log, loglevel=logging.INFO, raiseExceptions=False) |
2149 | parser.parseString(self.text) |
2150 | log.removeHandler(handler) |
2151 | - self.check_text() |
2152 | |
2153 | def check_text(self): |
2154 | """Call each line_method for each line in text.""" |
2155 | @@ -398,22 +444,29 @@ |
2156 | self.check_conflicts(line_no, line) |
2157 | self.check_tab(line_no, line) |
2158 | |
2159 | + def check_css_coding_conventions(self): |
2160 | + """Check the input using CSS Coding Convention checker.""" |
2161 | + CSSCodingConventionChecker(self.text, logger=self.message).check() |
2162 | + |
2163 | |
2164 | class PythonChecker(BaseChecker, AnyTextMixin): |
2165 | """Check python source code.""" |
2166 | |
2167 | + # This regex is taken from PEP 0263. |
2168 | + encoding_pattern = re.compile("coding[:=]\s*([-\w.]+)") |
2169 | + |
2170 | def __init__(self, file_path, text, reporter=None): |
2171 | super(PythonChecker, self).__init__( |
2172 | file_path, text, reporter=reporter) |
2173 | - self.is_utf8 = False |
2174 | + self.encoding = 'ascii' |
2175 | |
2176 | def check(self): |
2177 | """Check the syntax of the python code.""" |
2178 | if self.text == '': |
2179 | return |
2180 | + self.check_text() |
2181 | self.check_flakes() |
2182 | self.check_pep8() |
2183 | - self.check_text() |
2184 | |
2185 | def check_flakes(self): |
2186 | """Check compilation and syntax.""" |
2187 | @@ -421,13 +474,12 @@ |
2188 | tree = compiler.parse(self.text) |
2189 | except (SyntaxError, IndentationError), exc: |
2190 | line_no = exc.lineno or 0 |
2191 | - offset = exc.offset or 0 |
2192 | line = exc.text or '' |
2193 | explanation = 'Could not compile; %s' % exc.msg |
2194 | message = '%s: %s' % (explanation, line.strip()) |
2195 | self.message(line_no, message, icon='error') |
2196 | else: |
2197 | - warnings = Checker(tree) |
2198 | + warnings = PocketLintPyFlakesChecker(tree) |
2199 | for warning in warnings.messages: |
2200 | dummy, line_no, message = str(warning).split(':') |
2201 | self.message(int(line_no), message.strip(), icon='error') |
2202 | @@ -448,20 +500,24 @@ |
2203 | except TokenError, er: |
2204 | message, location = er.args |
2205 | self.message(location[0], message, icon='error') |
2206 | + except IndentationError, er: |
2207 | + message, location = er.args |
2208 | + message = "%s: %s" % (message, location[3].strip()) |
2209 | + self.message(location[1], message, icon='error') |
2210 | finally: |
2211 | - Checker.report_error = original_report_error |
2212 | + pep8.Checker.report_error = original_report_error |
2213 | |
2214 | def check_text(self): |
2215 | """Call each line_method for each line in text.""" |
2216 | for line_no, line in enumerate(self.text.splitlines()): |
2217 | line_no += 1 |
2218 | - if line_no in (1, 2) and '# -*- coding: utf-8 -*-' in line: |
2219 | - self.is_utf8 = True |
2220 | + if line_no in (1, 2): |
2221 | + match = self.encoding_pattern.search(line) |
2222 | + if match: |
2223 | + self.encoding = match.group(1).lower() |
2224 | self.check_pdb(line_no, line) |
2225 | self.check_length(line_no, line) |
2226 | - self.check_trailing_whitespace(line_no, line) |
2227 | self.check_conflicts(line_no, line) |
2228 | - self.check_tab(line_no, line) |
2229 | self.check_ascii(line_no, line) |
2230 | |
2231 | def check_pdb(self, line_no, line): |
2232 | @@ -473,10 +529,10 @@ |
2233 | |
2234 | def check_ascii(self, line_no, line): |
2235 | """Check that the line is ascii.""" |
2236 | - if self.is_utf8: |
2237 | + if self.encoding != 'ascii': |
2238 | return |
2239 | try: |
2240 | - ascii_line = line.encode('ascii') |
2241 | + line.encode('ascii') |
2242 | except UnicodeEncodeError, error: |
2243 | self.message( |
2244 | line_no, 'Non-ascii characer at position %s.' % error.end, |
2245 | @@ -494,7 +550,7 @@ |
2246 | """Check the syntax of the javascript code.""" |
2247 | if JS is None or self.text == '': |
2248 | return |
2249 | - args = [JS, '-f', self.FULLJSLINT, self.JSREPORTER, self.text] |
2250 | + args = [JS, self.JSREPORTER, self.FULLJSLINT, self.file_path] |
2251 | jslint = subprocess.Popen( |
2252 | args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
2253 | issues, errors = jslint.communicate() |
2254 | @@ -502,7 +558,9 @@ |
2255 | if issues: |
2256 | for issue in issues.splitlines(): |
2257 | line_no, char_no_, message = issue.split('::') |
2258 | - self.message(int(line_no), message, icon='error') |
2259 | + line_no = int(line_no) |
2260 | + line_no -= 1 |
2261 | + self.message(line_no, message, icon='error') |
2262 | self.check_text() |
2263 | |
2264 | def check_text(self): |
2265 | @@ -530,6 +588,7 @@ |
2266 | def check_sources(sources, reporter=None): |
2267 | if reporter is None: |
2268 | reporter = Reporter(Reporter.CONSOLE) |
2269 | + reporter.call_count = 0 |
2270 | for source in sources: |
2271 | file_path = os.path.normpath(source) |
2272 | if os.path.isdir(source) or not Language.is_editable(source): |
2273 | @@ -540,6 +599,7 @@ |
2274 | checker = UniversalChecker( |
2275 | file_path, text=text, language=language, reporter=reporter) |
2276 | checker.check() |
2277 | + return reporter.call_count |
2278 | |
2279 | |
2280 | def main(argv=None): |
2281 | @@ -554,7 +614,7 @@ |
2282 | if options.verbose: |
2283 | pass |
2284 | reporter = Reporter(Reporter.CONSOLE) |
2285 | - check_sources(sources, reporter) |
2286 | + return check_sources(sources, reporter) |
2287 | |
2288 | |
2289 | if __name__ == '__main__': |
2290 | |
2291 | === modified file 'pocketlint/jsreporter.js' |
2292 | --- pocketlint/jsreporter.js 2011-04-15 13:37:44 +0000 |
2293 | +++ pocketlint/jsreporter.js 2011-10-14 21:24:26 +0000 |
2294 | @@ -1,44 +1,101 @@ |
2295 | -// Copyright (C) 2009-2010 - Curtis Hovey <sinzui.is at verizon.net> |
2296 | +// Copyright (C) 2009-2011 - Curtis Hovey <sinzui.is at verizon.net> |
2297 | // This software is licensed under the MIT license (see the file COPYING). |
2298 | |
2299 | +// Run like: |
2300 | +// <seed|gjs> jsreporter.js <path/to/fulljslint.js> <path/file/to/lint.js> |
2301 | + |
2302 | + |
2303 | +function get_seed() { |
2304 | + // Define a common global object like seed. |
2305 | + var argv = ['gjs', 'jsreporter.js']; |
2306 | + var i; |
2307 | + for (i = 0; i < ARGV.length; i++) { |
2308 | + argv.push(ARGV[i]); |
2309 | + } |
2310 | + return { |
2311 | + 'print': print, |
2312 | + 'argv': argv |
2313 | + }; |
2314 | + } |
2315 | + |
2316 | + |
2317 | +var Seed = Seed || get_seed(); |
2318 | + |
2319 | + |
2320 | +jslint_path = Seed.argv[2].substring(0, Seed.argv[2].lastIndexOf('/')); |
2321 | +imports.searchPath.push(jslint_path); |
2322 | +var JSLINT = imports.fulljslint.JSLINT; |
2323 | + |
2324 | + |
2325 | +function get_file_content(file_path) { |
2326 | + // Return the content of the file. |
2327 | + var Gio = imports.gi.Gio; |
2328 | + var file = Gio.file_new_for_path(file_path); |
2329 | + var istream = file.read(null); |
2330 | + var dstream = new Gio.DataInputStream({base_stream: istream}); |
2331 | + var content_and_count = dstream.read_upto("", -1, null); |
2332 | + istream.close(null); |
2333 | + dstream = null; |
2334 | + return content_and_count[0]; |
2335 | + } |
2336 | + |
2337 | + |
2338 | function report_implied_names() { |
2339 | // Report about implied global names. |
2340 | var implied_names = []; |
2341 | - for (var name in JSLINT.implied) { |
2342 | - if (JSLINT.implied.hasOwnPropery(name)) { |
2343 | - implied_names.push(name); |
2344 | + var prop; |
2345 | + for (prop in JSLINT.implied) { |
2346 | + if (JSLINT.implied.hasOwnProperty(prop)) { |
2347 | + implied_names.push(prop); |
2348 | } |
2349 | } |
2350 | if (implied_names.length > 0) { |
2351 | implied_names.sort(); |
2352 | - print('0::0::Implied globals:' + implied_names.join(', ')); |
2353 | + return '0::0::Implied globals:' + implied_names.join(', '); |
2354 | } |
2355 | + return ''; |
2356 | } |
2357 | |
2358 | |
2359 | function report_lint_errors() { |
2360 | // Report about lint errors. |
2361 | - for (var i = 0; i < JSLINT.errors.length; i++) { |
2362 | + var errors = []; |
2363 | + var i; |
2364 | + for (i = 0; i < JSLINT.errors.length; i++) { |
2365 | var error = JSLINT.errors[i]; |
2366 | if (error === null) { |
2367 | - print('0::0::JSLINT had a fatal error.'); |
2368 | + error = { |
2369 | + 'line': -1, |
2370 | + 'character': -1, |
2371 | + 'reason': 'JSLINT had a fatal error.' |
2372 | + }; |
2373 | } |
2374 | // Fix the line and character offset for editors. |
2375 | error.line += 1; |
2376 | error.character += 1; |
2377 | - print(error.line + '::' + error.character + '::' + error.reason); |
2378 | + errors.push( |
2379 | + [error.line, error.character, error.reason].join('::')); |
2380 | } |
2381 | + return errors.join('\n'); |
2382 | } |
2383 | |
2384 | |
2385 | -function main(source_script) { |
2386 | +function lint_script() { |
2387 | // Lint the source and report errors. |
2388 | - var result = JSLINT(source_script); |
2389 | + var script = get_file_content(Seed.argv[3]); |
2390 | + var result = JSLINT(script); |
2391 | if (! result) { |
2392 | - report_lint_errors(); |
2393 | - report_implied_names(); |
2394 | + var issues = []; |
2395 | + errors = report_lint_errors(); |
2396 | + if (errors) { |
2397 | + issues.push(errors); |
2398 | + } |
2399 | + implied = report_implied_names(); |
2400 | + if (implied) { |
2401 | + issues.push(implied); |
2402 | + } |
2403 | + Seed.print(issues.join('\n')); |
2404 | } |
2405 | } |
2406 | |
2407 | - |
2408 | -main(arguments[0]); |
2409 | +lint_script(); |
2410 | |
2411 | === modified file 'setup.py' (properties changed: -x to +x) |
2412 | --- setup.py 2011-04-16 15:32:48 +0000 |
2413 | +++ setup.py 2011-10-14 21:24:26 +0000 |
2414 | @@ -1,9 +1,26 @@ |
2415 | +import subprocess |
2416 | + |
2417 | from distutils.core import setup |
2418 | +from distutils.command.sdist import sdist |
2419 | + |
2420 | + |
2421 | +class SignedSDistCommand(sdist): |
2422 | + """Sign the source archive with a detached signature.""" |
2423 | + |
2424 | + description = "Sign the source archive after it is generated." |
2425 | + |
2426 | + def run(self): |
2427 | + sdist.run(self) |
2428 | + gpg_args = [ |
2429 | + 'gpg', '--armor', '--sign', '--detach-sig', self.archive_files[0]] |
2430 | + gpg = subprocess.Popen( |
2431 | + gpg_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
2432 | + gpg.communicate() |
2433 | |
2434 | setup( |
2435 | name="pocketlint", |
2436 | description="Pocket-lint a composite linter and style checker.", |
2437 | - version="0.5.13", |
2438 | + version="0.5.21", |
2439 | maintainer="Curtis C. Hovey", |
2440 | maintainer_email="sinzui.is@verizon.net", |
2441 | url="https://launchpad.net/pocket-lint", |
2442 | @@ -17,4 +34,7 @@ |
2443 | 'pocketlint/contrib': ['fulljslint.js'], |
2444 | }, |
2445 | scripts=['scripts/pocketlint'], |
2446 | + cmdclass={ |
2447 | + 'signed_sdist': SignedSDistCommand, |
2448 | + }, |
2449 | ) |
It looks like debian/copyright needs updating at least for './pocketlint/ contrib/ cssccc. py', which is listed as "public domain", unlike the other files that are MIT.
The rest of it looks good to me.