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