Merge lp:~adiroiban/pocket-lint/pocket-lint-css-lint into lp:pocket-lint
- pocket-lint-css-lint
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Curtis Hovey | ||||
Approved revision: | 378 | ||||
Merged at revision: | 374 | ||||
Proposed branch: | lp:~adiroiban/pocket-lint/pocket-lint-css-lint | ||||
Merge into: | lp:pocket-lint | ||||
Diff against target: |
1103 lines (+1040/-4) 4 files modified
pocketlint/contrib/cssccc.py (+492/-0) pocketlint/formatcheck.py (+17/-2) pocketlint/tests/test_css.py (+2/-2) pocketlint/tests/test_cssccc.py (+529/-0) |
||||
To merge this branch: | bzr merge lp:~adiroiban/pocket-lint/pocket-lint-css-lint | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey | code | Approve | |
Review via email:
|
Commit message
Description of the change
pocket lint should support checking CSS style.

Adi Roiban (adiroiban) wrote : | # |

Curtis Hovey (sinzui) wrote : | # |
Your library and integration looks good, but test_css is broken. test_css is getting the cssccc errors too.
I think we should treat this like the PythonChecker class where check() calls seperate methods for pyflakes and pep8. Maybe CSSChecker.check() chouls call check_cssutils() and check_cssccc(). Then the two tests modules could be tested separately. What do you think?
I am a little concerned that cssccc raises an error for an indented final brace. I am happy to change the good_css to make that test pass for now. I would like cssccc to permit subordinate indentation for the final brace in the future
I would like to factor CSSUtils out in the future because I think a faster grammar checker could be written that emphasises the current and future standards. Your library could be extended to do this.
- 378. By Adi Roiban
-
Allow indented closing braces. Integrate tests.

Adi Roiban (adiroiban) wrote : | # |
I know that the current code break the test suite.
I was not sure if such feature is wanted and this is why I did not invest more time into making the test_cssccc tests work together with test_ccc.
----
I am OK with changing it the code to not raise an error on the indented final brace.
This is also the way I prefer it... but from the blog post and readers comment I saw that this formatting is not that popular.
I was thinking to add these two ways of formatting last brace as options and users could decide their format... in a similar way that the opening brace is implemented.
So for pocket-lint you could chose your preferred format.
----
I am new to CSS world. In fact I also wrote this code as a pretext for learning more about CSS.
I am also new to CSSUtils and I don't know what are its weak points.
With a bit of feedback we could also tackler this issues.
-----
I have made some changes bases of what I understood from your comments.
Feel free to add any comments :)
Cheers
Here is the latest diff
----
=== modified file 'pocketlint/
--- pocketlint/
+++ pocketlint/
@@ -42,6 +42,7 @@
* add support for TAB as a separator / identation.
* add support for @media
'''
+from __future__ import with_statement
__version__ = '0.1.0'
@@ -55,17 +56,26 @@
AT_TEXT_RULES = ['import', 'charset', 'namespace']
AT_BLOCK_RULES = ['page', 'font-face']
# If you want
-# selector
+# selector,
+# selector2
# {
# property:
# }
-#IGNORED_MESSAGES = ['I013']
+#IGNORED_MESSAGES = ['I013', 'I014']
# If you want
+# selector,
# selector {
# property:
# }
-IGNORED_MESSAGES = ['I005']
+#IGNORED_MESSAGES = ['I005', 'I014']
+
+# If you want
+# selector,
+# selector2 {
+# property:
+# }
+IGNORED_MESSAGES = ['I005', 'I006']
class CSSRule(object):
@@ -218,6 +228,12 @@
)
+ if last_declaration != '\n ':
+ self.log(
+ start_line + offset,
+ 'I014',
+ 'Rule declarations should end indented on a single new line.',
+ )
class CSSStatementMem
=== modified file 'pocketlint/
--- pocketlint/
+++ pocketlint/
@@ -382,27 +382,37 @@
if self.text == '':
return
- if HAS_CSSUTILS:
- handler = CSSReporterHand
- log = logging.
- log.addHandler(
- parser = cssutils.CSSParser(
- log=log, loglevel=
- parser.
- log.removeHandl
-
+ self.check_
+ # CSS coding conventoins checks should go last since they rely
+ # on previous checks.
+ self.check_
+
+ def check_cssutils(
+ """Check the CSS code by parsing it using CSSUtils module."""...

Curtis Hovey (sinzui) wrote : | # |
Thank you very much. I am merging this and building the package in the unstable PPA. If this proves to be reliable, I will copy it to stable and launchpad's ppa.

Adi Roiban (adiroiban) wrote : | # |
Hi,
Thanks for the merge.
I have added the unstable PPA and will report any problem... but I am not an hardcore CSS users/developers so I am not sure how much testing can I cover.
I will also look into the CSSUtils replacement part.
Right now I am monkey patching CSSUtils to recognize some of the CSS3 tags or browser specific tags and I would like to stop doing that :)
Cheers
Preview Diff
1 | === added file 'pocketlint/contrib/cssccc.py' |
2 | --- pocketlint/contrib/cssccc.py 1970-01-01 00:00:00 +0000 |
3 | +++ pocketlint/contrib/cssccc.py 2011-04-18 01:45:49 +0000 |
4 | @@ -0,0 +1,492 @@ |
5 | +''' |
6 | +This code is in the public domain. |
7 | + |
8 | +Check CSS code for some common coding conventions. |
9 | +The code must be in a valid CSS format. |
10 | +It is recommend to first parse it using cssutils. |
11 | +It is also recommend to check it with pocket-lint for things like trailing |
12 | +spaces or tab characters. |
13 | + |
14 | +If a comment is on the whole line, it will consume the whole line like it |
15 | +was not there. |
16 | +If a comment is inside a line it will only consume its own content. |
17 | + |
18 | +Bases on Stoyan Stefanov's http://www.phpied.com/css-coding-conventions/ |
19 | + |
20 | +'@media' rule is not supported. |
21 | + @media print { |
22 | + html { |
23 | + background: #fff; |
24 | + color: #000; |
25 | + } |
26 | + body { |
27 | + padding: 1in; |
28 | + border: 0.5pt solid #666; |
29 | + } |
30 | + } |
31 | + |
32 | +The following at-rules are supported: |
33 | + * keyword / text at-rules |
34 | + * @charset "ISO-8859-15"; |
35 | + * @import url(/css/screen.css) screen, projection; |
36 | + * @namespace foo "http://example.com/ns/foo"; |
37 | + * keybord / block rules |
38 | + * @page { block; } |
39 | + * @font-face { block; } |
40 | + |
41 | + |
42 | +TODO: |
43 | + * add warning for using px for fonts. |
44 | + * add Unicode support. |
45 | + * add AtRule checks |
46 | + * add support for TAB as a separator / identation. |
47 | + * add support for @media |
48 | +''' |
49 | +from __future__ import with_statement |
50 | + |
51 | +__version__ = '0.1.0' |
52 | + |
53 | +import sys |
54 | + |
55 | +SELECTOR_SEPARATOR = ',' |
56 | +DECLARATION_SEPARATOR = ';' |
57 | +PROPERTY_SEPARATOR = ':' |
58 | +COMMENT_START = r'/*' |
59 | +COMMENT_END = r'*/' |
60 | +AT_TEXT_RULES = ['import', 'charset', 'namespace'] |
61 | +AT_BLOCK_RULES = ['page', 'font-face'] |
62 | +# If you want |
63 | +# selector, |
64 | +# selector2 |
65 | +# { |
66 | +# property: |
67 | +# } |
68 | +#IGNORED_MESSAGES = ['I013', 'I014'] |
69 | + |
70 | +# If you want |
71 | +# selector, |
72 | +# selector { |
73 | +# property: |
74 | +# } |
75 | +#IGNORED_MESSAGES = ['I005', 'I014'] |
76 | + |
77 | +# If you want |
78 | +# selector, |
79 | +# selector2 { |
80 | +# property: |
81 | +# } |
82 | +IGNORED_MESSAGES = ['I005', 'I006'] |
83 | + |
84 | + |
85 | +class CSSRule(object): |
86 | + '''A CSS rule.''' |
87 | + |
88 | + def check(self): |
89 | + '''Check the rule.''' |
90 | + raise AssertionError('Method not implemtned.') |
91 | + |
92 | + |
93 | +class CSSAtRule(object): |
94 | + '''A CSS @rule.''' |
95 | + |
96 | + type = object() |
97 | + |
98 | + def __init__(self, identifier, keyword, log, text=None, block=None): |
99 | + self.identifier = identifier |
100 | + self.keyword = keyword |
101 | + self.text = text |
102 | + self.block = block |
103 | + self.log = log |
104 | + |
105 | + def check(self): |
106 | + '''Check the rule.''' |
107 | + |
108 | + |
109 | +class CSSRuleSet(object): |
110 | + '''A CSS rule_set.''' |
111 | + |
112 | + type = object() |
113 | + |
114 | + def __init__(self, selector, declarations, log): |
115 | + self.selector = selector |
116 | + self.declarations = declarations |
117 | + self.log = log |
118 | + |
119 | + def __str__(self): |
120 | + return '%s{%s}' % (str(self.selector), str(self.declarations)) |
121 | + |
122 | + def __repr__(self): |
123 | + return '%d:%s{%s}' % ( |
124 | + self.selector.start_line, |
125 | + str(self.selector), |
126 | + str(self.declarations), |
127 | + ) |
128 | + |
129 | + def check(self): |
130 | + '''Check the rule set.''' |
131 | + self.checkSelector() |
132 | + self.checkDeclarations() |
133 | + |
134 | + def checkSelector(self): |
135 | + '''Check rule-set selector.''' |
136 | + start_line = self.selector.getStartLine() |
137 | + selectors = self.selector.text.split(SELECTOR_SEPARATOR) |
138 | + offset = 0 |
139 | + last_selector = selectors[-1] |
140 | + first_selector = selectors[0] |
141 | + rest_selectors = selectors[1:] |
142 | + |
143 | + if first_selector.startswith('\n\n\n'): |
144 | + self.log(start_line, 'I002', 'To many newlines before selectors.') |
145 | + elif first_selector.startswith('\n\n'): |
146 | + pass |
147 | + elif start_line > 2: |
148 | + self.log(start_line, 'I003', 'To few newlines before selectors.') |
149 | + else: |
150 | + pass |
151 | + |
152 | + for selector in rest_selectors: |
153 | + if not selector.startswith('\n'): |
154 | + self.log( |
155 | + start_line + offset, |
156 | + 'I004', |
157 | + 'Selector must be on a new line.') |
158 | + offset += selector.count('\n') |
159 | + |
160 | + if not last_selector.endswith('\n'): |
161 | + self.log( |
162 | + start_line + offset, |
163 | + 'I005', |
164 | + 'No newline after last selector.') |
165 | + |
166 | + if not (last_selector[-2] != ' ' and last_selector[-1] == (' ')): |
167 | + self.log( |
168 | + start_line + offset, |
169 | + 'I013', |
170 | + 'Last selector must be followed by " {".') |
171 | + |
172 | + def checkDeclarations(self): |
173 | + '''Check rule-set declarations.''' |
174 | + start_line = self.declarations.getStartLine() |
175 | + declarations = self.declarations.text.split(DECLARATION_SEPARATOR) |
176 | + offset = 0 |
177 | + |
178 | + # Check all declarations except last as this is the new line. |
179 | + first_declaration = True |
180 | + for declaration in declarations[:-1]: |
181 | + if not declaration.startswith('\n'): |
182 | + self.log( |
183 | + start_line + offset, |
184 | + 'I007', |
185 | + 'Each declarations should start on a new line.', |
186 | + ) |
187 | + elif (not declaration.startswith('\n ') or |
188 | + declaration[5] == ' '): |
189 | + self.log( |
190 | + start_line + offset, |
191 | + 'I008', |
192 | + 'Each declaration must be indented with 4 spaces.', |
193 | + ) |
194 | + |
195 | + parts = declaration.split(PROPERTY_SEPARATOR) |
196 | + if len(parts) != 2: |
197 | + self.log( |
198 | + start_line + offset, |
199 | + 'I009', |
200 | + 'Wrong separator on property: value pair.', |
201 | + ) |
202 | + else: |
203 | + prop, value = parts |
204 | + if prop.endswith(' '): |
205 | + self.log( |
206 | + start_line + offset, |
207 | + 'I010', |
208 | + 'Whitespace before ":".', |
209 | + ) |
210 | + if not (value.startswith(' ') or value.startswith('\n')): |
211 | + self.log( |
212 | + start_line + offset, |
213 | + 'I011', |
214 | + 'Missing whitespace after ":".', |
215 | + ) |
216 | + elif value.startswith(' '): |
217 | + self.log( |
218 | + start_line + offset, |
219 | + 'I012', |
220 | + 'Multiple whitespaces after ":".', |
221 | + ) |
222 | + if first_declaration: |
223 | + first_declaration = False |
224 | + else: |
225 | + offset += declaration.count('\n') |
226 | + |
227 | + last_declaration = declarations[-1] |
228 | + offset += last_declaration.count('\n') |
229 | + if last_declaration != '\n': |
230 | + self.log( |
231 | + start_line + offset, |
232 | + 'I006', |
233 | + 'Rule declarations should end with a single new line.', |
234 | + ) |
235 | + if last_declaration != '\n ': |
236 | + self.log( |
237 | + start_line + offset, |
238 | + 'I014', |
239 | + 'Rule declarations should end indented on a single new line.', |
240 | + ) |
241 | + |
242 | + |
243 | +class CSSStatementMember(object): |
244 | + '''A member of CSS statement.''' |
245 | + |
246 | + def __init__(self, start_line, start_character, text): |
247 | + self.start_line = start_line |
248 | + self.start_character = start_character |
249 | + self.text = text |
250 | + |
251 | + def getStartLine(self): |
252 | + '''Return the line number for first character in the statement and |
253 | + the number of new lines untilg the first character.''' |
254 | + index = 0 |
255 | + text = self.text |
256 | + character = text[index] |
257 | + while character == '\n': |
258 | + index += 1 |
259 | + character = text[index] |
260 | + |
261 | + return self.start_line + index + 1 |
262 | + |
263 | + def __str__(self): |
264 | + return self.text |
265 | + |
266 | + def __repr__(self): |
267 | + return '%d:%d:{%s}' % ( |
268 | + self.start_line, self.start_character, self.text) |
269 | + |
270 | + |
271 | +class CSSCodingConventionChecker(object): |
272 | + '''CSS coding convention checker.''' |
273 | + |
274 | + icons = { |
275 | + 'E': 'error', |
276 | + 'I': 'info', |
277 | + } |
278 | + |
279 | + def __init__(self, text, logger=None): |
280 | + self._text = text.splitlines(True) |
281 | + self.line_number = 0 |
282 | + self.character_number = 0 |
283 | + if logger: |
284 | + self._logger = logger |
285 | + else: |
286 | + self._logger = self._defaultLog |
287 | + |
288 | + def log(self, line_number, code, message): |
289 | + '''Log the message with `code`.''' |
290 | + if code in IGNORED_MESSAGES: |
291 | + return |
292 | + icon = self.icons[code[0]] |
293 | + self._logger(line_number, code + ': ' + message, icon=icon) |
294 | + |
295 | + def check(self): |
296 | + '''Check all rules.''' |
297 | + for rule in self.getRules(): |
298 | + rule.check() |
299 | + |
300 | + def getRules(self): |
301 | + '''Generates the next CSS rule ignoring comments.''' |
302 | + while True: |
303 | + yield self.getNextRule() |
304 | + |
305 | + def getNextRule(self): |
306 | + '''Return the next parsed rule. |
307 | + |
308 | + Raise `StopIteration` if we are at the last rule. |
309 | + ''' |
310 | + if self._nextStatementIsAtRule(): |
311 | + text = None |
312 | + block = None |
313 | + keyword = self._parse('@') |
314 | + # TODO: user regex [ \t {] |
315 | + keyword_text = self._parse(' ') |
316 | + keyword_name = keyword_text.text |
317 | + keyword.text += '@' + keyword_name + ' ' |
318 | + |
319 | + if keyword_name.lower() in AT_TEXT_RULES: |
320 | + text = self._parse(';') |
321 | + elif keyword_name.lower() in AT_BLOCK_RULES: |
322 | + start = self._parse('{') |
323 | + keyword.text += start.text |
324 | + block = self._parse('}') |
325 | + else: |
326 | + self._parse(';') |
327 | + raise StopIteration |
328 | + |
329 | + return CSSAtRule( |
330 | + identifier=keyword_name, |
331 | + keyword=keyword, |
332 | + text=text, |
333 | + block=block, |
334 | + log=self.log) |
335 | + else: |
336 | + selector = self._parse('{') |
337 | + declarations = self._parse('}') |
338 | + return CSSRuleSet( |
339 | + selector=selector, |
340 | + declarations=declarations, |
341 | + log=self.log) |
342 | + |
343 | + def _defaultLog(self, line_number, message, icon='info'): |
344 | + '''Log the message to STDOUT.''' |
345 | + print ' %4s:%s' % (line_number, message) |
346 | + |
347 | + def _nextStatementIsAtRule(self): |
348 | + '''Return True if next statement in the buffer is an at-rule. |
349 | + |
350 | + Just look for open brackets and see if there is an @ before that |
351 | + braket. |
352 | + ''' |
353 | + search_buffer = [] |
354 | + line_counter = self.line_number |
355 | + current_line = self._text[line_counter][self.character_number:] |
356 | + while current_line.find('@') == -1: |
357 | + search_buffer.append(current_line) |
358 | + line_counter += 1 |
359 | + try: |
360 | + current_line = self._text[line_counter] |
361 | + except IndexError: |
362 | + return False |
363 | + |
364 | + text_buffer = ''.join(search_buffer) |
365 | + if text_buffer.find('{') == -1: |
366 | + return True |
367 | + else: |
368 | + return False |
369 | + |
370 | + def _parse(self, stop_character): |
371 | + '''Return the parsed text until stop_character.''' |
372 | + try: |
373 | + self._text[self.line_number][self.character_number] |
374 | + except IndexError: |
375 | + raise StopIteration |
376 | + result = [] |
377 | + start_line = self.line_number |
378 | + start_character = self.character_number |
379 | + comment_started = False |
380 | + while True: |
381 | + try: |
382 | + data = self._text[self.line_number][self.character_number:] |
383 | + except IndexError: |
384 | + break |
385 | + |
386 | + # Look for comment start/end. |
387 | + comment_check = _check_comment(data) |
388 | + (comment_update, |
389 | + before_comment, |
390 | + after_comment, |
391 | + newline_consumed) = comment_check |
392 | + if comment_update is not None: |
393 | + comment_started = comment_update |
394 | + |
395 | + if comment_started: |
396 | + # We are inside a comment. |
397 | + # Add the data before the comment and go to next line. |
398 | + if before_comment is not None: |
399 | + result.append(before_comment) |
400 | + self.character_number = 0 |
401 | + self.line_number += 1 |
402 | + continue |
403 | + |
404 | + # If we have a comment, strip it from the data. |
405 | + # Remember the initial cursor position to know where to |
406 | + # continue. |
407 | + initial_position = data.find(stop_character) |
408 | + if before_comment is not None or after_comment is not None: |
409 | + if before_comment is None: |
410 | + before_comment = '' |
411 | + if after_comment is None: |
412 | + after_comment = '' |
413 | + data = before_comment + after_comment |
414 | + |
415 | + if initial_position == -1 or newline_consumed: |
416 | + # We are not at the end. |
417 | + # Go to next line and append the data. |
418 | + result.append(data) |
419 | + self.character_number = 0 |
420 | + self.line_number += 1 |
421 | + continue |
422 | + else: |
423 | + # Delimiter found. |
424 | + # Find it again in the text that now has no comments. |
425 | + # Append data until the delimiter. |
426 | + # Move cursor to next character and stop searching for it. |
427 | + new_position = data.find(stop_character) |
428 | + result.append(data[:new_position]) |
429 | + self.character_number += initial_position + 1 |
430 | + break |
431 | + |
432 | + return CSSStatementMember( |
433 | + start_line=start_line, |
434 | + start_character=start_character, |
435 | + text=''.join(result)) |
436 | + |
437 | + |
438 | +def _check_comment(data): |
439 | + '''Check the data for comment markers.''' |
440 | + |
441 | + comment_started = None |
442 | + before_comment = None |
443 | + after_comment = None |
444 | + newline_consumed = False |
445 | + comment_start = data.find(COMMENT_START) |
446 | + if comment_start != -1: |
447 | + comment_started = True |
448 | + before_comment = data[:comment_start] |
449 | + |
450 | + comment_end = data.find(COMMENT_END) |
451 | + if comment_end != -1: |
452 | + comment_started = False |
453 | + # Comment end after the lenght of the actual comment end |
454 | + # marker. |
455 | + comment_end += len(COMMENT_END) |
456 | + if before_comment is None and data[comment_end] == '\n': |
457 | + # Consume the new line if it next to the comment end and |
458 | + # the comment in on the whole line. |
459 | + comment_end += 1 |
460 | + newline_consumed = True |
461 | + after_comment = data[comment_end:] |
462 | + return (comment_started, before_comment, after_comment, newline_consumed) |
463 | + |
464 | + |
465 | +def show_usage(): |
466 | + '''Print the command usage.''' |
467 | + print 'Usage: cssccc OPTIONS' |
468 | + print ' -h, --help\t\tShow this help.' |
469 | + print ' -v, --version\t\tShow version.' |
470 | + print ' -f FILE, --file=FILE\tCheck FILE' |
471 | + |
472 | + |
473 | +def read_file(filename): |
474 | + '''Return the content of filename.''' |
475 | + text = '' |
476 | + with open(filename, 'r') as f: |
477 | + text = f.read() |
478 | + return text |
479 | + |
480 | + |
481 | +if __name__ == '__main__': |
482 | + if len(sys.argv) < 2: |
483 | + show_usage() |
484 | + elif sys.argv[1] in ['-v', '--version']: |
485 | + print 'CSS Code Convention Checker %s' % (__version__) |
486 | + sys.exit(0) |
487 | + elif sys.argv[1] == '-f': |
488 | + text = read_file(sys.argv[2]) |
489 | + checker = CSSCodingConventionChecker(text) |
490 | + sys.exit(checker.check()) |
491 | + elif sys.argv[1] == '--file=': |
492 | + text = read_file(sys.argv[1][len('--file='):]) |
493 | + checker = CSSCodingConventionChecker(text) |
494 | + sys.exit(checker.check()) |
495 | + else: |
496 | + show_usage() |
497 | |
498 | === modified file 'pocketlint/formatcheck.py' |
499 | --- pocketlint/formatcheck.py 2011-04-17 16:32:41 +0000 |
500 | +++ pocketlint/formatcheck.py 2011-04-18 01:45:49 +0000 |
501 | @@ -31,6 +31,7 @@ |
502 | from formatdoctest import DoctestReviewer |
503 | |
504 | import contrib.pep8 as pep8 |
505 | +from contrib.cssccc import CSSCodingConventionChecker |
506 | from contrib.pyflakes.checker import Checker |
507 | try: |
508 | import cssutils |
509 | @@ -396,7 +397,18 @@ |
510 | |
511 | def check(self): |
512 | """Check the syntax of the CSS code.""" |
513 | - if self.text == '' or not HAS_CSSUTILS: |
514 | + if self.text == '': |
515 | + return |
516 | + |
517 | + self.check_cssutils() |
518 | + self.check_text() |
519 | + # CSS coding conventoins checks should go last since they rely |
520 | + # on previous checks. |
521 | + self.check_css_coding_conventions() |
522 | + |
523 | + def check_cssutils(self): |
524 | + """Check the CSS code by parsing it using CSSUtils module.""" |
525 | + if not HAS_CSSUTILS: |
526 | return |
527 | handler = CSSReporterHandler(self) |
528 | log = logging.getLogger('pocket-lint') |
529 | @@ -405,7 +417,6 @@ |
530 | log=log, loglevel=logging.INFO, raiseExceptions=False) |
531 | parser.parseString(self.text) |
532 | log.removeHandler(handler) |
533 | - self.check_text() |
534 | |
535 | def check_text(self): |
536 | """Call each line_method for each line in text.""" |
537 | @@ -416,6 +427,10 @@ |
538 | self.check_conflicts(line_no, line) |
539 | self.check_tab(line_no, line) |
540 | |
541 | + def check_css_coding_conventions(self): |
542 | + """Check the input using CSS Coding Convention checker.""" |
543 | + CSSCodingConventionChecker(self.text, logger=self.message).check() |
544 | + |
545 | |
546 | class PythonChecker(BaseChecker, AnyTextMixin): |
547 | """Check python source code.""" |
548 | |
549 | === modified file 'pocketlint/tests/test_css.py' |
550 | --- pocketlint/tests/test_css.py 2011-04-15 03:14:05 +0000 |
551 | +++ pocketlint/tests/test_css.py 2011-04-18 01:45:49 +0000 |
552 | @@ -41,7 +41,7 @@ |
553 | if not HAS_CSSUTILS: |
554 | return |
555 | checker = CSSChecker('bogus', ill_formed_property, self.reporter) |
556 | - checker.check() |
557 | + checker.check_cssutils() |
558 | messages = [ |
559 | (3, "CSSValue: No match: 'CHAR', u':'"), |
560 | (0, 'CSSStyleDeclaration: Syntax Error in Property: ' |
561 | @@ -52,7 +52,7 @@ |
562 | if not HAS_CSSUTILS: |
563 | return |
564 | checker = CSSChecker('ballyhoo', invalid_value, self.reporter) |
565 | - checker.check() |
566 | + checker.check_cssutils() |
567 | message = ( |
568 | 'Invalid value for "CSS Color Module Level 3/CSS Level 2.1" ' |
569 | 'property: speckled: color') |
570 | |
571 | === added file 'pocketlint/tests/test_cssccc.py' |
572 | --- pocketlint/tests/test_cssccc.py 1970-01-01 00:00:00 +0000 |
573 | +++ pocketlint/tests/test_cssccc.py 2011-04-18 01:45:49 +0000 |
574 | @@ -0,0 +1,529 @@ |
575 | +'''Test module for cssccc''' |
576 | + |
577 | +from unittest import TestCase, main as unittest_main |
578 | + |
579 | + |
580 | +from pocketlint.contrib.cssccc import ( |
581 | + CSSCodingConventionChecker, CSSAtRule, CSSRuleSet, CSSStatementMember) |
582 | + |
583 | + |
584 | +class TestCSSCodingConventionChecker(TestCase): |
585 | + '''Test for parsing the CSS text.''' |
586 | + |
587 | + def test_getNextRule_start(self): |
588 | + text = 'selector{}' |
589 | + lint = CSSCodingConventionChecker(text) |
590 | + rule = lint.getNextRule() |
591 | + self.assertTrue(rule.type is CSSRuleSet.type) |
592 | + self.assertEqual('selector', rule.selector.text) |
593 | + self.assertEqual(0, rule.selector.start_line) |
594 | + self.assertEqual(0, rule.selector.start_character) |
595 | + |
596 | + text = '\nselector{}' |
597 | + lint = CSSCodingConventionChecker(text) |
598 | + rule = lint.getNextRule() |
599 | + self.assertTrue(rule.type is CSSRuleSet.type) |
600 | + self.assertEqual('\nselector', rule.selector.text) |
601 | + self.assertEqual(0, rule.selector.start_line) |
602 | + self.assertEqual(0, rule.selector.start_character) |
603 | + |
604 | + text = '\n\nselector{}' |
605 | + lint = CSSCodingConventionChecker(text) |
606 | + rule = lint.getNextRule() |
607 | + self.assertTrue(rule.type is CSSRuleSet.type) |
608 | + self.assertEqual('\n\nselector', rule.selector.text) |
609 | + self.assertEqual(0, rule.selector.start_line) |
610 | + self.assertEqual(0, rule.selector.start_character) |
611 | + |
612 | + text = 'selector\n{}' |
613 | + lint = CSSCodingConventionChecker(text) |
614 | + rule = lint.getNextRule() |
615 | + self.assertTrue(rule.type is CSSRuleSet.type) |
616 | + self.assertEqual('selector\n', rule.selector.text) |
617 | + self.assertEqual(0, rule.selector.start_line) |
618 | + self.assertEqual(0, rule.selector.start_character) |
619 | + |
620 | + text = 'selector, {}' |
621 | + lint = CSSCodingConventionChecker(text) |
622 | + rule = lint.getNextRule() |
623 | + self.assertTrue(rule.type is CSSRuleSet.type) |
624 | + self.assertEqual('selector, ', rule.selector.text) |
625 | + self.assertEqual(0, rule.selector.start_line) |
626 | + self.assertEqual(0, rule.selector.start_character) |
627 | + |
628 | + def test_getNextRule_content(self): |
629 | + text = 'selector { content; }' |
630 | + lint = CSSCodingConventionChecker(text) |
631 | + rule = lint.getNextRule() |
632 | + self.assertTrue(rule.type is CSSRuleSet.type) |
633 | + self.assertEqual(' content; ', rule.declarations.text) |
634 | + self.assertEqual(0, rule.declarations.start_line) |
635 | + self.assertEqual(10, rule.declarations.start_character) |
636 | + |
637 | + text = 'selector \n{\n content; }' |
638 | + lint = CSSCodingConventionChecker(text) |
639 | + rule = lint.getNextRule() |
640 | + self.assertTrue(rule.type is CSSRuleSet.type) |
641 | + self.assertEqual('\n content; ', rule.declarations.text) |
642 | + self.assertEqual(1, rule.declarations.start_line) |
643 | + self.assertEqual(1, rule.declarations.start_character) |
644 | + |
645 | + def test_getNextRule_continue(self): |
646 | + text = 'selector1\n { content1; }\n\nselector2\n{content2}\n' |
647 | + lint = CSSCodingConventionChecker(text) |
648 | + rule = lint.getNextRule() |
649 | + self.assertTrue(rule.type is CSSRuleSet.type) |
650 | + self.assertEqual('selector1\n ', rule.selector.text) |
651 | + self.assertEqual(0, rule.selector.start_line) |
652 | + self.assertEqual(0, rule.selector.start_character) |
653 | + self.assertEqual(' content1; ', rule.declarations.text) |
654 | + self.assertEqual(1, rule.declarations.start_line) |
655 | + self.assertEqual(2, rule.declarations.start_character) |
656 | + |
657 | + rule = lint.getNextRule() |
658 | + self.assertTrue(rule.type is CSSRuleSet.type) |
659 | + self.assertEqual('\n\nselector2\n', rule.selector.text) |
660 | + self.assertEqual(1, rule.selector.start_line) |
661 | + self.assertEqual(14, rule.selector.start_character) |
662 | + self.assertEqual('content2', rule.declarations.text) |
663 | + self.assertEqual(4, rule.declarations.start_line) |
664 | + self.assertEqual(1, rule.declarations.start_character) |
665 | + |
666 | + def test_getNextRule_stop(self): |
667 | + text ='rule1{st1\n}\n@font-face {\n src: url("u\n u"); \n }\nr2{st2}' |
668 | + lint = CSSCodingConventionChecker(text) |
669 | + rule = lint.getNextRule() |
670 | + self.assertTrue(rule.type is CSSRuleSet.type) |
671 | + rule = lint.getNextRule() |
672 | + self.assertTrue(rule.type is CSSAtRule.type) |
673 | + rule = lint.getNextRule() |
674 | + self.assertTrue(rule.type is CSSRuleSet.type) |
675 | + self.failUnlessRaises(StopIteration, lint.getNextRule) |
676 | + |
677 | + def test_getNextRule_comment(self): |
678 | + text = '\n\n/* c\nm*/\nsel\n{\ns/*com*/\ncont1;/*com*/\ncont2;}' |
679 | + lint = CSSCodingConventionChecker(text) |
680 | + rule = lint.getNextRule() |
681 | + self.assertTrue(rule.type is CSSRuleSet.type) |
682 | + self.assertEqual('\n\nsel\n', rule.selector.text) |
683 | + self.assertEqual(0, rule.selector.start_line) |
684 | + self.assertEqual(0, rule.selector.start_character) |
685 | + self.assertEqual('\ns\ncont1;\ncont2;', rule.declarations.text) |
686 | + self.assertEqual(5, rule.declarations.start_line) |
687 | + self.assertEqual(1, rule.declarations.start_character) |
688 | + |
689 | + def test_get_at_import_rule(self): |
690 | + '''Test for @import url(/css/screen.css) screen, projection;''' |
691 | + text ='rule1{st1\n}\n@import url(somet) print, soment ;rule2{st2}' |
692 | + lint = CSSCodingConventionChecker(text) |
693 | + rule = lint.getNextRule() |
694 | + self.assertTrue(rule.type is CSSRuleSet.type) |
695 | + rule = lint.getNextRule() |
696 | + self.assertTrue(rule.type is CSSAtRule.type) |
697 | + self.assertTrue(rule.block is None) |
698 | + self.assertEqual('import', rule.identifier) |
699 | + self.assertEqual('\n@import ', rule.keyword.text) |
700 | + self.assertEqual(1, rule.keyword.start_line) |
701 | + self.assertEqual(1, rule.keyword.start_character) |
702 | + self.assertEqual(' url(somet) print, soment ', rule.text.text) |
703 | + self.assertEqual(2, rule.text.start_line) |
704 | + self.assertEqual(8, rule.text.start_character) |
705 | + |
706 | + def test_get_at_charset_rule(self): |
707 | + '''Test for @charset "ISO-8859-15";''' |
708 | + text ='rule1{st1\n}\n@charset "utf" ;rule2{st2}' |
709 | + lint = CSSCodingConventionChecker(text) |
710 | + rule = lint.getNextRule() |
711 | + self.assertTrue(rule.type is CSSRuleSet.type) |
712 | + rule = lint.getNextRule() |
713 | + self.assertTrue(rule.type is CSSAtRule.type) |
714 | + self.assertTrue(rule.block is None) |
715 | + self.assertEqual('charset', rule.identifier) |
716 | + self.assertEqual('\n@charset ', rule.keyword.text) |
717 | + self.assertEqual(1, rule.keyword.start_line) |
718 | + self.assertEqual(1, rule.keyword.start_character) |
719 | + self.assertEqual(' "utf" ', rule.text.text) |
720 | + self.assertEqual(2, rule.text.start_line) |
721 | + self.assertEqual(9, rule.text.start_character) |
722 | + |
723 | + def test_get_at_namespace_rule(self): |
724 | + '''Test for @namespace foo "http://foo" ;''' |
725 | + text ='rule1{st1\n}@namespace foo "http://foo" ;rule2{st2}' |
726 | + lint = CSSCodingConventionChecker(text) |
727 | + rule = lint.getNextRule() |
728 | + self.assertTrue(rule.type is CSSRuleSet.type) |
729 | + rule = lint.getNextRule() |
730 | + self.assertTrue(rule.type is CSSAtRule.type) |
731 | + self.assertTrue(rule.block is None) |
732 | + self.assertEqual('namespace', rule.identifier) |
733 | + self.assertEqual('@namespace ', rule.keyword.text) |
734 | + self.assertEqual(1, rule.keyword.start_line) |
735 | + self.assertEqual(1, rule.keyword.start_character) |
736 | + self.assertEqual(' foo "http://foo" ', rule.text.text) |
737 | + self.assertEqual(1, rule.text.start_line) |
738 | + self.assertEqual(12, rule.text.start_character) |
739 | + |
740 | + def test_get_at_page_rule(self): |
741 | + '''Test for @page |
742 | + |
743 | + @page :left { |
744 | + margin-left: 5cm; /* left pages only */ |
745 | + } |
746 | + ''' |
747 | + text ='rule1{st1\n}\n@page :left {\n mar; /*com*/\n }\nrule2{st2}' |
748 | + lint = CSSCodingConventionChecker(text) |
749 | + rule = lint.getNextRule() |
750 | + self.assertTrue(rule.type is CSSRuleSet.type) |
751 | + rule = lint.getNextRule() |
752 | + self.assertTrue(rule.type is CSSAtRule.type) |
753 | + self.assertTrue(rule.text is None) |
754 | + self.assertEqual('page', rule.identifier) |
755 | + self.assertEqual('\n@page :left ', rule.keyword.text) |
756 | + self.assertEqual(1, rule.keyword.start_line) |
757 | + self.assertEqual(1, rule.keyword.start_character) |
758 | + self.assertEqual('\n mar; \n ', rule.block.text) |
759 | + self.assertEqual(2, rule.block.start_line) |
760 | + self.assertEqual(13, rule.block.start_character) |
761 | + |
762 | + def test_get_at_font_face_rule(self): |
763 | + '''Test for @font-face |
764 | + |
765 | + @font-face { |
766 | + font-family: "Example Font"; |
767 | + src: url("http://www.example.com |
768 | + /fonts/example"); |
769 | + } |
770 | + ''' |
771 | + text ='rule1{st1\n}\n@font-face {\n src: url("u\n u"); \n }\nr2{st2}' |
772 | + lint = CSSCodingConventionChecker(text) |
773 | + rule = lint.getNextRule() |
774 | + self.assertTrue(rule.type is CSSRuleSet.type) |
775 | + rule = lint.getNextRule() |
776 | + self.assertTrue(rule.type is CSSAtRule.type) |
777 | + self.assertTrue(rule.text is None) |
778 | + self.assertEqual('font-face', rule.identifier) |
779 | + self.assertEqual('\n@font-face ', rule.keyword.text) |
780 | + self.assertEqual(1, rule.keyword.start_line) |
781 | + self.assertEqual(1, rule.keyword.start_character) |
782 | + self.assertEqual('\n src: url("u\n u"); \n ', rule.block.text) |
783 | + self.assertEqual(2, rule.block.start_line) |
784 | + self.assertEqual(12, rule.block.start_character) |
785 | + rule = lint.getNextRule() |
786 | + self.assertTrue(rule.type is CSSRuleSet.type) |
787 | + self.failUnlessRaises(StopIteration, lint.getNextRule) |
788 | + |
789 | + |
790 | +class TestCSSStatementMember(TestCase): |
791 | + '''Tests for CSSStatementMember.''' |
792 | + |
793 | + def test_getStartLine(self): |
794 | + statement = CSSStatementMember(0, 4, 'some') |
795 | + self.assertEqual(1, statement.getStartLine()) |
796 | + statement = CSSStatementMember(3, 4, 'some') |
797 | + self.assertEqual(4, statement.getStartLine()) |
798 | + statement = CSSStatementMember(3, 4, '\n\nsome') |
799 | + self.assertEqual(6, statement.getStartLine()) |
800 | + |
801 | + |
802 | +class TestLog(object): |
803 | + '''Container for a test log.''' |
804 | + |
805 | + def __init__(self, line_number, code, message): |
806 | + self.line_number = line_number |
807 | + self.code = code |
808 | + self.message = message |
809 | + |
810 | + |
811 | +class RuleTesterBase(TestCase): |
812 | + '''Base class for rule checkers.''' |
813 | + |
814 | + ignored_messaged = [] |
815 | + |
816 | + def setUp(self): |
817 | + self.logs = [] |
818 | + |
819 | + def log(self, line_number, code, message): |
820 | + if code in self.ignored_messaged: |
821 | + return |
822 | + self.logs.append((line_number, code, message)) |
823 | + |
824 | + @property |
825 | + def last_log(self): |
826 | + (line_number, code, message) = self.logs.pop() |
827 | + return TestLog(line_number, code, message) |
828 | + |
829 | + |
830 | +class RuleTesterConventionA(RuleTesterBase): |
831 | + '''Class for convention A. |
832 | + |
833 | + selector1, |
834 | + selecter2 |
835 | + { |
836 | + property1: value1; |
837 | + property2: value2; |
838 | + } |
839 | + ''' |
840 | + |
841 | + ignored_messaged = ['I013', 'I014'] |
842 | + |
843 | + |
844 | +class TestCSSRuleSetSelectorChecksA(RuleTesterConventionA): |
845 | + '''Test coding conventions for selector from rule sets.''' |
846 | + |
847 | + def test_valid_selector(self): |
848 | + |
849 | + selector = CSSStatementMember(0, 0, 'something\n') |
850 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
851 | + rule.checkSelector() |
852 | + self.assertEqual([], self.logs) |
853 | + |
854 | + selector = CSSStatementMember(0, 0, '\nsomething\n') |
855 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
856 | + rule.checkSelector() |
857 | + self.assertEqual([], self.logs) |
858 | + |
859 | + selector = CSSStatementMember(1, 0, '\n\nsomething\n') |
860 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
861 | + rule.checkSelector() |
862 | + self.assertEqual([], self.logs) |
863 | + |
864 | + selector = CSSStatementMember(2, 0, '\n\nsomething,\nsomethi\n') |
865 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
866 | + rule.checkSelector() |
867 | + self.assertEqual([], self.logs) |
868 | + |
869 | + selector = CSSStatementMember(3, 0, '\n\nsom:some some,\n#somethi\n') |
870 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
871 | + rule.checkSelector() |
872 | + self.assertEqual([], self.logs) |
873 | + |
874 | + def test_I002(self): |
875 | + selector = CSSStatementMember(2, 0, '\n\n\nsomething\n') |
876 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
877 | + rule.checkSelector() |
878 | + last_log = self.last_log |
879 | + self.assertEqual('I002', last_log.code) |
880 | + self.assertEqual(6, last_log.line_number) |
881 | + |
882 | + selector = CSSStatementMember(4, 0, '\n\n\n\nsomething\n') |
883 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
884 | + rule.checkSelector() |
885 | + last_log = self.last_log |
886 | + self.assertEqual('I002', last_log.code) |
887 | + self.assertEqual(9, last_log.line_number) |
888 | + |
889 | + def test_I003(self): |
890 | + selector = CSSStatementMember(2, 0, '\nsomething\n') |
891 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
892 | + rule.checkSelector() |
893 | + last_log = self.last_log |
894 | + self.assertEqual('I003', last_log.code) |
895 | + self.assertEqual(4, last_log.line_number) |
896 | + |
897 | + selector = CSSStatementMember(2, 0, 'something\n') |
898 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
899 | + rule.checkSelector() |
900 | + last_log = self.last_log |
901 | + self.assertEqual('I003', last_log.code) |
902 | + self.assertEqual(3, last_log.line_number) |
903 | + |
904 | + def test_I004(self): |
905 | + selector = CSSStatementMember(3, 0, '\n\nsomething, something\n') |
906 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
907 | + rule.checkSelector() |
908 | + last_log = self.last_log |
909 | + self.assertEqual('I004', last_log.code) |
910 | + self.assertEqual(6, last_log.line_number) |
911 | + |
912 | + def test_I005(self): |
913 | + selector = CSSStatementMember(4, 0, '\nsomething,\nsomething') |
914 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
915 | + rule.checkSelector() |
916 | + last_log = self.last_log |
917 | + self.assertEqual('I005', last_log.code) |
918 | + self.assertEqual(7, last_log.line_number) |
919 | + |
920 | + |
921 | +class TestCSSRuleSetDeclarationsChecksA(RuleTesterConventionA): |
922 | + '''Test coding conventions for declarations from rule sets.''' |
923 | + |
924 | + def test_valid_declarations(self): |
925 | + stmt = CSSStatementMember( |
926 | + 0, 0, '\n some: 3px;\n other:\n url();\n') |
927 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
928 | + rule.checkDeclarations() |
929 | + self.assertEqual([], self.logs) |
930 | + |
931 | + def test_I006(self): |
932 | + stmt = CSSStatementMember( |
933 | + 4, 0, '\n some: 3px;\n other: url();') |
934 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
935 | + rule.checkDeclarations() |
936 | + last_log = self.last_log |
937 | + self.assertEqual('I006', last_log.code) |
938 | + self.assertEqual(7, last_log.line_number) |
939 | + |
940 | + stmt = CSSStatementMember( |
941 | + 4, 0, '\n some: 3px;\n other: url();\n ') |
942 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
943 | + rule.checkDeclarations() |
944 | + last_log = self.last_log |
945 | + self.assertEqual('I006', last_log.code) |
946 | + self.assertEqual(8, last_log.line_number) |
947 | + |
948 | + stmt = CSSStatementMember( |
949 | + 4, 0, '\n some: 3px;\n other: url();\n\n ') |
950 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
951 | + rule.checkDeclarations() |
952 | + last_log = self.last_log |
953 | + self.assertEqual('I006', last_log.code) |
954 | + self.assertEqual(9, last_log.line_number) |
955 | + |
956 | + def test_I007(self): |
957 | + stmt = CSSStatementMember( |
958 | + 4, 0, '\n some: 3px; other: url();\n') |
959 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
960 | + rule.checkDeclarations() |
961 | + last_log = self.last_log |
962 | + self.assertEqual('I007', last_log.code) |
963 | + self.assertEqual(6, last_log.line_number) |
964 | + |
965 | + def test_I008(self): |
966 | + stmt = CSSStatementMember( |
967 | + 0, 0, '\n some: 3px;\n other: url();\n') |
968 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
969 | + rule.checkDeclarations() |
970 | + self.assertEqual('I008', self.last_log.code) |
971 | + |
972 | + stmt = CSSStatementMember( |
973 | + 0, 0, '\n some: 3px;\n other: url();\n') |
974 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
975 | + rule.checkDeclarations() |
976 | + self.assertEqual('I008', self.last_log.code) |
977 | + |
978 | + def test_I009(self): |
979 | + stmt = CSSStatementMember( |
980 | + 0, 0, '\n some 3px;\n other: url();\n') |
981 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
982 | + rule.checkDeclarations() |
983 | + self.assertEqual('I009', self.last_log.code) |
984 | + |
985 | + stmt = CSSStatementMember( |
986 | + 0, 0, '\n some: 3:px;\n other: url();\n') |
987 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
988 | + rule.checkDeclarations() |
989 | + self.assertEqual('I009', self.last_log.code) |
990 | + |
991 | + def test_I010(self): |
992 | + stmt = CSSStatementMember( |
993 | + 0, 0, '\n some : 3px;\n') |
994 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
995 | + rule.checkDeclarations() |
996 | + self.assertEqual('I010', self.last_log.code) |
997 | + |
998 | + def test_I011(self): |
999 | + stmt = CSSStatementMember( |
1000 | + 0, 0, '\n some:3px;\n') |
1001 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
1002 | + rule.checkDeclarations() |
1003 | + self.assertEqual('I011', self.last_log.code) |
1004 | + |
1005 | + def test_I012(self): |
1006 | + stmt = CSSStatementMember( |
1007 | + 0, 0, '\n some: 3px;\n') |
1008 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
1009 | + rule.checkDeclarations() |
1010 | + self.assertEqual('I012', self.last_log.code) |
1011 | + |
1012 | + |
1013 | +class RuleTesterConventionB(RuleTesterBase): |
1014 | + '''Class for convention B. |
1015 | + |
1016 | + selector1, |
1017 | + selecter2 { |
1018 | + property1: value1; |
1019 | + property2: value2; |
1020 | + } |
1021 | + ''' |
1022 | + |
1023 | + ignored_messaged = ['I005', 'I014'] |
1024 | + |
1025 | + |
1026 | +class TestCSSRuleSetSelectorChecksB(RuleTesterConventionB): |
1027 | + '''Test coding conventions for selector from rule sets.''' |
1028 | + |
1029 | + def test_valid_selector(self): |
1030 | + |
1031 | + selector = CSSStatementMember(0, 0, 'something ') |
1032 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1033 | + rule.checkSelector() |
1034 | + self.assertEqual([], self.logs) |
1035 | + |
1036 | + selector = CSSStatementMember(0, 0, '\nsomething ') |
1037 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1038 | + rule.checkSelector() |
1039 | + self.assertEqual([], self.logs) |
1040 | + |
1041 | + selector = CSSStatementMember(1, 0, '\n\nsomething ') |
1042 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1043 | + rule.checkSelector() |
1044 | + self.assertEqual([], self.logs) |
1045 | + |
1046 | + selector = CSSStatementMember(2, 0, '\n\nsomething,\nsomethi ') |
1047 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1048 | + rule.checkSelector() |
1049 | + self.assertEqual([], self.logs) |
1050 | + |
1051 | + selector = CSSStatementMember(3, 0, '\n\nsom:some some,\n#somethi ') |
1052 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1053 | + rule.checkSelector() |
1054 | + self.assertEqual([], self.logs) |
1055 | + |
1056 | + def test_I013(self): |
1057 | + selector = CSSStatementMember(2, 0, '\n\nsomething\n') |
1058 | + rule = CSSRuleSet(selector=selector, declarations=None, log=self.log) |
1059 | + rule.checkSelector() |
1060 | + last_log = self.last_log |
1061 | + self.assertEqual('I013', last_log.code) |
1062 | + self.assertEqual(5, last_log.line_number) |
1063 | + |
1064 | + |
1065 | +class RuleTesterConventionC(RuleTesterBase): |
1066 | + '''Class for convention C. |
1067 | + |
1068 | + selector1, |
1069 | + selecter2 { |
1070 | + property1: value1; |
1071 | + property2: value2; |
1072 | + } |
1073 | + ''' |
1074 | + |
1075 | + ignored_messaged = ['I005', 'I006'] |
1076 | + |
1077 | + |
1078 | +class TestCSSRuleSetDeclarationsChecksC(RuleTesterConventionC): |
1079 | + '''Test coding conventions for declarations from rule sets.''' |
1080 | + |
1081 | + def test_valid_declarations(self): |
1082 | + stmt = CSSStatementMember( |
1083 | + 0, 0, '\n some: 3px;\n other:\n url();\n ') |
1084 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
1085 | + rule.checkDeclarations() |
1086 | + self.assertEqual([], self.logs) |
1087 | + |
1088 | + def test_I014(self): |
1089 | + stmt = CSSStatementMember( |
1090 | + 0, 0, '\n some: 3px;\n') |
1091 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
1092 | + rule.checkDeclarations() |
1093 | + self.assertEqual('I014', self.last_log.code) |
1094 | + |
1095 | + stmt = CSSStatementMember( |
1096 | + 0, 0, '\n some: 3px;\n ') |
1097 | + rule = CSSRuleSet(selector=None, declarations=stmt, log=self.log) |
1098 | + rule.checkDeclarations() |
1099 | + self.assertEqual('I014', self.last_log.code) |
1100 | + |
1101 | + |
1102 | +if __name__ == '__main__': |
1103 | + unittest_main() |
The test input texts are a bit cryptic.
Maybe I could put them as module constants and define them using multiline string... but then they will be far from the actual test.
Since they are not that long, I think that string concatenation on multiple lines could also solve this problem
So instead of
text ='rule1{ st1\n}\ n@font- face {\n src: url("u\n u"); \n }\nr2{st2}'
It would be
text = (
'rule1{ st1\n'
'}\n'
'@font-face {\n'
' src: url("u\n u"); \n'
' }\n'
'r2{st2}')