Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: third_party/closure_linter/closure_linter/ecmalintrules.py

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2008 The Closure Linter Authors. All Rights Reserved.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS-IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 """Core methods for checking EcmaScript files for common style guide violations.
18 """
19
20 __author__ = ('robbyw@google.com (Robert Walker)',
21 'ajp@google.com (Andy Perelson)',
22 'jacobr@google.com (Jacob Richman)')
23
24 import re
25
26 import gflags as flags
27
28 from closure_linter import checkerbase
29 from closure_linter import ecmametadatapass
30 from closure_linter import error_check
31 from closure_linter import errorrules
32 from closure_linter import errors
33 from closure_linter import indentation
34 from closure_linter import javascripttokenizer
35 from closure_linter import javascripttokens
36 from closure_linter import statetracker
37 from closure_linter import tokenutil
38 from closure_linter.common import error
39 from closure_linter.common import position
40
41
42 FLAGS = flags.FLAGS
43 flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow')
44 # TODO(user): When flipping this to True, remove logic from unit tests
45 # that overrides this flag.
46 flags.DEFINE_boolean('dot_on_next_line', False, 'Require dots to be'
47 'placed on the next line for wrapped expressions')
48
49 flags.DEFINE_boolean('check_trailing_comma', False, 'Check trailing commas'
50 ' (ES3, not needed from ES5 onwards)')
51
52 # TODO(robbyw): Check for extra parens on return statements
53 # TODO(robbyw): Check for 0px in strings
54 # TODO(robbyw): Ensure inline jsDoc is in {}
55 # TODO(robbyw): Check for valid JS types in parameter docs
56
57 # Shorthand
58 Context = ecmametadatapass.EcmaContext
59 Error = error.Error
60 Modes = javascripttokenizer.JavaScriptModes
61 Position = position.Position
62 Rule = error_check.Rule
63 Type = javascripttokens.JavaScriptTokenType
64
65
66 class EcmaScriptLintRules(checkerbase.LintRulesBase):
67 """EmcaScript lint style checking rules.
68
69 Can be used to find common style errors in JavaScript, ActionScript and other
70 Ecma like scripting languages. Style checkers for Ecma scripting languages
71 should inherit from this style checker.
72 Please do not add any state to EcmaScriptLintRules or to any subclasses.
73
74 All state should be added to the StateTracker subclass used for a particular
75 language.
76 """
77
78 # It will be initialized in constructor so the flags are initialized.
79 max_line_length = -1
80
81 # Static constants.
82 MISSING_PARAMETER_SPACE = re.compile(r',\S')
83
84 EXTRA_SPACE = re.compile(r'(\(\s|\s\))')
85
86 ENDS_WITH_SPACE = re.compile(r'\s$')
87
88 ILLEGAL_TAB = re.compile(r'\t')
89
90 # Regex used to split up complex types to check for invalid use of ? and |.
91 TYPE_SPLIT = re.compile(r'[,<>()]')
92
93 # Regex for form of author lines after the @author tag.
94 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)')
95
96 # Acceptable tokens to remove for line too long testing.
97 LONG_LINE_IGNORE = frozenset(
98 ['*', '//', '@see'] +
99 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE])
100
101 JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([
102 '@fileoverview', '@param', '@return', '@returns'])
103
104 def __init__(self):
105 """Initialize this lint rule object."""
106 checkerbase.LintRulesBase.__init__(self)
107 if EcmaScriptLintRules.max_line_length == -1:
108 EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength()
109
110 def Initialize(self, checker, limited_doc_checks, is_html):
111 """Initialize this lint rule object before parsing a new file."""
112 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks,
113 is_html)
114 self._indentation = indentation.IndentationRules()
115
116 def HandleMissingParameterDoc(self, token, param_name):
117 """Handle errors associated with a parameter missing a @param tag."""
118 raise TypeError('Abstract method HandleMissingParameterDoc not implemented')
119
120 def _CheckLineLength(self, last_token, state):
121 """Checks whether the line is too long.
122
123 Args:
124 last_token: The last token in the line.
125 state: parser_state object that indicates the current state in the page
126 """
127 # Start from the last token so that we have the flag object attached to
128 # and DOC_FLAG tokens.
129 line_number = last_token.line_number
130 token = last_token
131
132 # Build a representation of the string where spaces indicate potential
133 # line-break locations.
134 line = []
135 while token and token.line_number == line_number:
136 if state.IsTypeToken(token):
137 line.insert(0, 'x' * len(token.string))
138 elif token.type in (Type.IDENTIFIER, Type.OPERATOR):
139 # Dots are acceptable places to wrap (may be tokenized as identifiers).
140 line.insert(0, token.string.replace('.', ' '))
141 else:
142 line.insert(0, token.string)
143 token = token.previous
144
145 line = ''.join(line)
146 line = line.rstrip('\n\r\f')
147 try:
148 length = len(unicode(line, 'utf-8'))
149 except (LookupError, UnicodeDecodeError):
150 # Unknown encoding. The line length may be wrong, as was originally the
151 # case for utf-8 (see bug 1735846). For now just accept the default
152 # length, but as we find problems we can either add test for other
153 # possible encodings or return without an error to protect against
154 # false positives at the cost of more false negatives.
155 length = len(line)
156
157 if length > EcmaScriptLintRules.max_line_length:
158
159 # If the line matches one of the exceptions, then it's ok.
160 for long_line_regexp in self.GetLongLineExceptions():
161 if long_line_regexp.match(last_token.line):
162 return
163
164 # If the line consists of only one "word", or multiple words but all
165 # except one are ignoreable, then it's ok.
166 parts = set(line.split())
167
168 # We allow two "words" (type and name) when the line contains @param
169 max_parts = 1
170 if '@param' in parts:
171 max_parts = 2
172
173 # Custom tags like @requires may have url like descriptions, so ignore
174 # the tag, similar to how we handle @see.
175 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags])
176 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags))
177 > max_parts):
178 self._HandleError(
179 errors.LINE_TOO_LONG,
180 'Line too long (%d characters).' % len(line), last_token)
181
182 def _CheckJsDocType(self, token, js_type):
183 """Checks the given type for style errors.
184
185 Args:
186 token: The DOC_FLAG token for the flag whose type to check.
187 js_type: The flag's typeannotation.TypeAnnotation instance.
188 """
189 if not js_type: return
190
191 if js_type.type_group and len(js_type.sub_types) == 2:
192 identifiers = [t.identifier for t in js_type.sub_types]
193 if 'null' in identifiers:
194 # Don't warn if the identifier is a template type (e.g. {TYPE|null}.
195 if not identifiers[0].isupper() and not identifiers[1].isupper():
196 self._HandleError(
197 errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL,
198 'Prefer "?Type" to "Type|null": "%s"' % js_type, token)
199
200 # TODO(user): We should report an error for wrong usage of '?' and '|'
201 # e.g. {?number|string|null} etc.
202
203 for sub_type in js_type.IterTypes():
204 self._CheckJsDocType(token, sub_type)
205
206 def _CheckForMissingSpaceBeforeToken(self, token):
207 """Checks for a missing space at the beginning of a token.
208
209 Reports a MISSING_SPACE error if the token does not begin with a space or
210 the previous token doesn't end with a space and the previous token is on the
211 same line as the token.
212
213 Args:
214 token: The token being checked
215 """
216 # TODO(user): Check if too many spaces?
217 if (len(token.string) == len(token.string.lstrip()) and
218 token.previous and token.line_number == token.previous.line_number and
219 len(token.previous.string) - len(token.previous.string.rstrip()) == 0):
220 self._HandleError(
221 errors.MISSING_SPACE,
222 'Missing space before "%s"' % token.string,
223 token,
224 position=Position.AtBeginning())
225
226 def _CheckOperator(self, token):
227 """Checks an operator for spacing and line style.
228
229 Args:
230 token: The operator token.
231 """
232 last_code = token.metadata.last_code
233
234 if not self._ExpectSpaceBeforeOperator(token):
235 if (token.previous and token.previous.type == Type.WHITESPACE and
236 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER) and
237 last_code.line_number == token.line_number):
238 self._HandleError(
239 errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string,
240 token.previous, position=Position.All(token.previous.string))
241
242 elif (token.previous and
243 not token.previous.IsComment() and
244 not tokenutil.IsDot(token) and
245 token.previous.type in Type.EXPRESSION_ENDER_TYPES):
246 self._HandleError(errors.MISSING_SPACE,
247 'Missing space before "%s"' % token.string, token,
248 position=Position.AtBeginning())
249
250 # Check wrapping of operators.
251 next_code = tokenutil.GetNextCodeToken(token)
252
253 is_dot = tokenutil.IsDot(token)
254 wrapped_before = last_code and last_code.line_number != token.line_number
255 wrapped_after = next_code and next_code.line_number != token.line_number
256
257 if FLAGS.dot_on_next_line and is_dot and wrapped_after:
258 self._HandleError(
259 errors.LINE_ENDS_WITH_DOT,
260 '"." must go on the following line',
261 token)
262 if (not is_dot and wrapped_before and
263 not token.metadata.IsUnaryOperator()):
264 self._HandleError(
265 errors.LINE_STARTS_WITH_OPERATOR,
266 'Binary operator must go on previous line "%s"' % token.string,
267 token)
268
269 def _IsLabel(self, token):
270 # A ':' token is considered part of a label if it occurs in a case
271 # statement, a plain label, or an object literal, i.e. is not part of a
272 # ternary.
273
274 return (token.string == ':' and
275 token.metadata.context.type in (Context.LITERAL_ELEMENT,
276 Context.CASE_BLOCK,
277 Context.STATEMENT))
278
279 def _ExpectSpaceBeforeOperator(self, token):
280 """Returns whether a space should appear before the given operator token.
281
282 Args:
283 token: The operator token.
284
285 Returns:
286 Whether there should be a space before the token.
287 """
288 if token.string == ',' or token.metadata.IsUnaryPostOperator():
289 return False
290
291 if tokenutil.IsDot(token):
292 return False
293
294 # Colons should appear in labels, object literals, the case of a switch
295 # statement, and ternary operator. Only want a space in the case of the
296 # ternary operator.
297 if self._IsLabel(token):
298 return False
299
300 if token.metadata.IsUnaryOperator() and token.IsFirstInLine():
301 return False
302
303 return True
304
305 def CheckToken(self, token, state):
306 """Checks a token, given the current parser_state, for warnings and errors.
307
308 Args:
309 token: The current token under consideration
310 state: parser_state object that indicates the current state in the page
311 """
312 # Store some convenience variables
313 first_in_line = token.IsFirstInLine()
314 last_in_line = token.IsLastInLine()
315 last_non_space_token = state.GetLastNonSpaceToken()
316
317 token_type = token.type
318
319 # Process the line change.
320 if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION):
321 # TODO(robbyw): Support checking indentation in HTML files.
322 indentation_errors = self._indentation.CheckToken(token, state)
323 for indentation_error in indentation_errors:
324 self._HandleError(*indentation_error)
325
326 if last_in_line:
327 self._CheckLineLength(token, state)
328
329 if token_type == Type.PARAMETERS:
330 # Find missing spaces in parameter lists.
331 if self.MISSING_PARAMETER_SPACE.search(token.string):
332 fix_data = ', '.join([s.strip() for s in token.string.split(',')])
333 self._HandleError(errors.MISSING_SPACE, 'Missing space after ","',
334 token, position=None, fix_data=fix_data.strip())
335
336 # Find extra spaces at the beginning of parameter lists. Make sure
337 # we aren't at the beginning of a continuing multi-line list.
338 if not first_in_line:
339 space_count = len(token.string) - len(token.string.lstrip())
340 if space_count:
341 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("',
342 token, position=Position(0, space_count))
343
344 elif (token_type == Type.START_BLOCK and
345 token.metadata.context.type == Context.BLOCK):
346 self._CheckForMissingSpaceBeforeToken(token)
347
348 elif token_type == Type.END_BLOCK:
349 last_code = token.metadata.last_code
350
351 if FLAGS.check_trailing_comma:
352 if last_code.IsOperator(','):
353 self._HandleError(
354 errors.COMMA_AT_END_OF_LITERAL,
355 'Illegal comma at end of object literal', last_code,
356 position=Position.All(last_code.string))
357
358 if state.InFunction() and state.IsFunctionClose():
359 if state.InTopLevelFunction():
360 # A semicolons should not be included at the end of a function
361 # declaration.
362 if not state.InAssignedFunction():
363 if not last_in_line and token.next.type == Type.SEMICOLON:
364 self._HandleError(
365 errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
366 'Illegal semicolon after function declaration',
367 token.next, position=Position.All(token.next.string))
368
369 # A semicolon should be included at the end of a function expression
370 # that is not immediately called or used by a dot operator.
371 if (state.InAssignedFunction() and token.next
372 and token.next.type != Type.SEMICOLON):
373 next_token = tokenutil.GetNextCodeToken(token)
374 is_immediately_used = next_token and (
375 next_token.type == Type.START_PAREN or
376 tokenutil.IsDot(next_token))
377 if not is_immediately_used:
378 self._HandleError(
379 errors.MISSING_SEMICOLON_AFTER_FUNCTION,
380 'Missing semicolon after function assigned to a variable',
381 token, position=Position.AtEnd(token.string))
382
383 if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK:
384 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
385 'Interface methods cannot contain code', last_code)
386
387 elif (state.IsBlockClose() and
388 token.next and token.next.type == Type.SEMICOLON):
389 if (last_code.metadata.context.parent.type != Context.OBJECT_LITERAL
390 and last_code.metadata.context.type != Context.OBJECT_LITERAL):
391 self._HandleError(
392 errors.REDUNDANT_SEMICOLON,
393 'No semicolon is required to end a code block',
394 token.next, position=Position.All(token.next.string))
395
396 elif token_type == Type.SEMICOLON:
397 if token.previous and token.previous.type == Type.WHITESPACE:
398 self._HandleError(
399 errors.EXTRA_SPACE, 'Extra space before ";"',
400 token.previous, position=Position.All(token.previous.string))
401
402 if token.next and token.next.line_number == token.line_number:
403 if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
404 # TODO(robbyw): Error about no multi-statement lines.
405 pass
406
407 elif token.next.type not in (
408 Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN):
409 self._HandleError(
410 errors.MISSING_SPACE,
411 'Missing space after ";" in for statement',
412 token.next,
413 position=Position.AtBeginning())
414
415 last_code = token.metadata.last_code
416 if last_code and last_code.type == Type.SEMICOLON:
417 # Allow a single double semi colon in for loops for cases like:
418 # for (;;) { }.
419 # NOTE(user): This is not a perfect check, and will not throw an error
420 # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
421 # probably won't work either.
422 for_token = tokenutil.CustomSearch(
423 last_code,
424 lambda token: token.type == Type.KEYWORD and token.string == 'for',
425 end_func=lambda token: token.type == Type.SEMICOLON,
426 distance=None,
427 reverse=True)
428
429 if not for_token:
430 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon',
431 token, position=Position.All(token.string))
432
433 elif token_type == Type.START_PAREN:
434 # Ensure that opening parentheses have a space before any keyword
435 # that is not being invoked like a member function.
436 if (token.previous and token.previous.type == Type.KEYWORD and
437 (not token.previous.metadata or
438 not token.previous.metadata.last_code or
439 not token.previous.metadata.last_code.string or
440 token.previous.metadata.last_code.string[-1:] != '.')):
441 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("',
442 token, position=Position.AtBeginning())
443 elif token.previous and token.previous.type == Type.WHITESPACE:
444 before_space = token.previous.previous
445 # Ensure that there is no extra space before a function invocation,
446 # even if the function being invoked happens to be a keyword.
447 if (before_space and before_space.line_number == token.line_number and
448 before_space.type == Type.IDENTIFIER or
449 (before_space.type == Type.KEYWORD and before_space.metadata and
450 before_space.metadata.last_code and
451 before_space.metadata.last_code.string and
452 before_space.metadata.last_code.string[-1:] == '.')):
453 self._HandleError(
454 errors.EXTRA_SPACE, 'Extra space before "("',
455 token.previous, position=Position.All(token.previous.string))
456
457 elif token_type == Type.START_BRACKET:
458 self._HandleStartBracket(token, last_non_space_token)
459 elif token_type in (Type.END_PAREN, Type.END_BRACKET):
460 # Ensure there is no space before closing parentheses, except when
461 # it's in a for statement with an omitted section, or when it's at the
462 # beginning of a line.
463
464 last_code = token.metadata.last_code
465 if FLAGS.check_trailing_comma and token_type == Type.END_BRACKET:
466 if last_code.IsOperator(','):
467 self._HandleError(
468 errors.COMMA_AT_END_OF_LITERAL,
469 'Illegal comma at end of array literal', last_code,
470 position=Position.All(last_code.string))
471
472 if (token.previous and token.previous.type == Type.WHITESPACE and
473 not token.previous.IsFirstInLine() and
474 not (last_non_space_token and last_non_space_token.line_number ==
475 token.line_number and
476 last_non_space_token.type == Type.SEMICOLON)):
477 self._HandleError(
478 errors.EXTRA_SPACE, 'Extra space before "%s"' %
479 token.string, token.previous,
480 position=Position.All(token.previous.string))
481
482 elif token_type == Type.WHITESPACE:
483 if self.ILLEGAL_TAB.search(token.string):
484 if token.IsFirstInLine():
485 if token.next:
486 self._HandleError(
487 errors.ILLEGAL_TAB,
488 'Illegal tab in whitespace before "%s"' % token.next.string,
489 token, position=Position.All(token.string))
490 else:
491 self._HandleError(
492 errors.ILLEGAL_TAB,
493 'Illegal tab in whitespace',
494 token, position=Position.All(token.string))
495 else:
496 self._HandleError(
497 errors.ILLEGAL_TAB,
498 'Illegal tab in whitespace after "%s"' % token.previous.string,
499 token, position=Position.All(token.string))
500
501 # Check whitespace length if it's not the first token of the line and
502 # if it's not immediately before a comment.
503 if last_in_line:
504 # Check for extra whitespace at the end of a line.
505 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
506 token, position=Position.All(token.string))
507 elif not first_in_line and not token.next.IsComment():
508 if token.length > 1:
509 self._HandleError(
510 errors.EXTRA_SPACE, 'Extra space after "%s"' %
511 token.previous.string, token,
512 position=Position(1, len(token.string) - 1))
513
514 elif token_type == Type.OPERATOR:
515 self._CheckOperator(token)
516 elif token_type == Type.DOC_FLAG:
517 flag = token.attached_object
518
519 if flag.flag_type == 'bug':
520 # TODO(robbyw): Check for exactly 1 space on the left.
521 string = token.next.string.lstrip()
522 string = string.split(' ', 1)[0]
523
524 if not string.isdigit():
525 self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
526 '@bug should be followed by a bug number', token)
527
528 elif flag.flag_type == 'suppress':
529 if flag.type is None:
530 # A syntactically invalid suppress tag will get tokenized as a normal
531 # flag, indicating an error.
532 self._HandleError(
533 errors.INCORRECT_SUPPRESS_SYNTAX,
534 'Invalid suppress syntax: should be @suppress {errortype}. '
535 'Spaces matter.', token)
536 else:
537 for suppress_type in flag.jstype.IterIdentifiers():
538 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES:
539 self._HandleError(
540 errors.INVALID_SUPPRESS_TYPE,
541 'Invalid suppression type: %s' % suppress_type, token)
542
543 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and
544 flag.flag_type == 'author'):
545 # TODO(user): In non strict mode check the author tag for as much as
546 # it exists, though the full form checked below isn't required.
547 string = token.next.string
548 result = self.AUTHOR_SPEC.match(string)
549 if not result:
550 self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION,
551 'Author tag line should be of the form: '
552 '@author foo@somewhere.com (Your Name)',
553 token.next)
554 else:
555 # Check spacing between email address and name. Do this before
556 # checking earlier spacing so positions are easier to calculate for
557 # autofixing.
558 num_spaces = len(result.group(2))
559 if num_spaces < 1:
560 self._HandleError(errors.MISSING_SPACE,
561 'Missing space after email address',
562 token.next, position=Position(result.start(2), 0))
563 elif num_spaces > 1:
564 self._HandleError(
565 errors.EXTRA_SPACE, 'Extra space after email address',
566 token.next,
567 position=Position(result.start(2) + 1, num_spaces - 1))
568
569 # Check for extra spaces before email address. Can't be too few, if
570 # not at least one we wouldn't match @author tag.
571 num_spaces = len(result.group(1))
572 if num_spaces > 1:
573 self._HandleError(errors.EXTRA_SPACE,
574 'Extra space before email address',
575 token.next, position=Position(1, num_spaces - 1))
576
577 elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and
578 not self._limited_doc_checks):
579 if flag.flag_type == 'param':
580 if flag.name is None:
581 self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
582 'Missing name in @param tag', token)
583
584 if not flag.description or flag.description is None:
585 flag_name = token.type
586 if 'name' in token.values:
587 flag_name = '@' + token.values['name']
588
589 if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED:
590 self._HandleError(
591 errors.MISSING_JSDOC_TAG_DESCRIPTION,
592 'Missing description in %s tag' % flag_name, token)
593 else:
594 self._CheckForMissingSpaceBeforeToken(flag.description_start_token)
595
596 if flag.HasType():
597 if flag.type_start_token is not None:
598 self._CheckForMissingSpaceBeforeToken(
599 token.attached_object.type_start_token)
600
601 if flag.jstype and not flag.jstype.IsEmpty():
602 self._CheckJsDocType(token, flag.jstype)
603
604 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and (
605 flag.type_start_token.type != Type.DOC_START_BRACE or
606 flag.type_end_token.type != Type.DOC_END_BRACE):
607 self._HandleError(
608 errors.MISSING_BRACES_AROUND_TYPE,
609 'Type must always be surrounded by curly braces.', token)
610
611 if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
612 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and
613 token.values['name'] not in FLAGS.custom_jsdoc_tags):
614 self._HandleError(
615 errors.INVALID_JSDOC_TAG,
616 'Invalid JsDoc tag: %s' % token.values['name'], token)
617
618 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and
619 token.values['name'] == 'inheritDoc' and
620 token_type == Type.DOC_INLINE_FLAG):
621 self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
622 'Unnecessary braces around @inheritDoc',
623 token)
624
625 elif token_type == Type.SIMPLE_LVALUE:
626 identifier = token.values['identifier']
627
628 if ((not state.InFunction() or state.InConstructor()) and
629 state.InTopLevel() and not state.InObjectLiteralDescendant()):
630 jsdoc = state.GetDocComment()
631 if not state.HasDocComment(identifier):
632 # Only test for documentation on identifiers with .s in them to
633 # avoid checking things like simple variables. We don't require
634 # documenting assignments to .prototype itself (bug 1880803).
635 if (not state.InConstructor() and
636 identifier.find('.') != -1 and not
637 identifier.endswith('.prototype') and not
638 self._limited_doc_checks):
639 comment = state.GetLastComment()
640 if not (comment and comment.lower().count('jsdoc inherited')):
641 self._HandleError(
642 errors.MISSING_MEMBER_DOCUMENTATION,
643 "No docs found for member '%s'" % identifier,
644 token)
645 elif jsdoc and (not state.InConstructor() or
646 identifier.startswith('this.')):
647 # We are at the top level and the function/member is documented.
648 if identifier.endswith('_') and not identifier.endswith('__'):
649 # Can have a private class which inherits documentation from a
650 # public superclass.
651 #
652 # @inheritDoc is deprecated in favor of using @override, and they
653 if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor')
654 and ('accessControls' not in jsdoc.suppressions)):
655 self._HandleError(
656 errors.INVALID_OVERRIDE_PRIVATE,
657 '%s should not override a private member.' % identifier,
658 jsdoc.GetFlag('override').flag_token)
659 if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor')
660 and ('accessControls' not in jsdoc.suppressions)):
661 self._HandleError(
662 errors.INVALID_INHERIT_DOC_PRIVATE,
663 '%s should not inherit from a private member.' % identifier,
664 jsdoc.GetFlag('inheritDoc').flag_token)
665 if (not jsdoc.HasFlag('private') and
666 ('underscore' not in jsdoc.suppressions) and not
667 ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and
668 ('accessControls' in jsdoc.suppressions))):
669 self._HandleError(
670 errors.MISSING_PRIVATE,
671 'Member "%s" must have @private JsDoc.' %
672 identifier, token)
673 if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions:
674 self._HandleError(
675 errors.UNNECESSARY_SUPPRESS,
676 '@suppress {underscore} is not necessary with @private',
677 jsdoc.suppressions['underscore'])
678 elif (jsdoc.HasFlag('private') and
679 not self.InExplicitlyTypedLanguage()):
680 # It is convention to hide public fields in some ECMA
681 # implementations from documentation using the @private tag.
682 self._HandleError(
683 errors.EXTRA_PRIVATE,
684 'Member "%s" must not have @private JsDoc' %
685 identifier, token)
686
687 # These flags are only legal on localizable message definitions;
688 # such variables always begin with the prefix MSG_.
689 if not identifier.startswith('MSG_') and '.MSG_' not in identifier:
690 for f in ('desc', 'hidden', 'meaning'):
691 if jsdoc.HasFlag(f):
692 self._HandleError(
693 errors.INVALID_USE_OF_DESC_TAG,
694 'Member "%s" does not start with MSG_ and thus '
695 'should not have @%s JsDoc' % (identifier, f),
696 token)
697
698 # Check for illegaly assigning live objects as prototype property values.
699 index = identifier.find('.prototype.')
700 # Ignore anything with additional .s after the prototype.
701 if index != -1 and identifier.find('.', index + 11) == -1:
702 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
703 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES)
704 if next_code and (
705 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or
706 next_code.IsOperator('new')):
707 self._HandleError(
708 errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
709 'Member %s cannot have a non-primitive value' % identifier,
710 token)
711
712 elif token_type == Type.END_PARAMETERS:
713 # Find extra space at the end of parameter lists. We check the token
714 # prior to the current one when it is a closing paren.
715 if (token.previous and token.previous.type == Type.PARAMETERS
716 and self.ENDS_WITH_SPACE.search(token.previous.string)):
717 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
718 token.previous)
719
720 jsdoc = state.GetDocComment()
721 if state.GetFunction().is_interface:
722 if token.previous and token.previous.type == Type.PARAMETERS:
723 self._HandleError(
724 errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
725 'Interface constructor cannot have parameters',
726 token.previous)
727 elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
728 and not jsdoc.InheritsDocumentation()
729 and not state.InObjectLiteralDescendant() and not
730 jsdoc.IsInvalidated()):
731 distance, edit = jsdoc.CompareParameters(state.GetParams())
732 if distance:
733 params_iter = iter(state.GetParams())
734 docs_iter = iter(jsdoc.ordered_params)
735
736 for op in edit:
737 if op == 'I':
738 # Insertion.
739 # Parsing doc comments is the same for all languages
740 # but some languages care about parameters that don't have
741 # doc comments and some languages don't care.
742 # Languages that don't allow variables to by typed such as
743 # JavaScript care but languages such as ActionScript or Java
744 # that allow variables to be typed don't care.
745 if not self._limited_doc_checks:
746 self.HandleMissingParameterDoc(token, params_iter.next())
747
748 elif op == 'D':
749 # Deletion
750 self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION,
751 'Found docs for non-existing parameter: "%s"' %
752 docs_iter.next(), token)
753 elif op == 'S':
754 # Substitution
755 if not self._limited_doc_checks:
756 self._HandleError(
757 errors.WRONG_PARAMETER_DOCUMENTATION,
758 'Parameter mismatch: got "%s", expected "%s"' %
759 (params_iter.next(), docs_iter.next()), token)
760
761 else:
762 # Equality - just advance the iterators
763 params_iter.next()
764 docs_iter.next()
765
766 elif token_type == Type.STRING_TEXT:
767 # If this is the first token after the start of the string, but it's at
768 # the end of a line, we know we have a multi-line string.
769 if token.previous.type in (
770 Type.SINGLE_QUOTE_STRING_START,
771 Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
772 self._HandleError(errors.MULTI_LINE_STRING,
773 'Multi-line strings are not allowed', token)
774
775 # This check is orthogonal to the ones above, and repeats some types, so
776 # it is a plain if and not an elif.
777 if token.type in Type.COMMENT_TYPES:
778 if self.ILLEGAL_TAB.search(token.string):
779 self._HandleError(errors.ILLEGAL_TAB,
780 'Illegal tab in comment "%s"' % token.string, token)
781
782 trimmed = token.string.rstrip()
783 if last_in_line and token.string != trimmed:
784 # Check for extra whitespace at the end of a line.
785 self._HandleError(
786 errors.EXTRA_SPACE, 'Extra space at end of line', token,
787 position=Position(len(trimmed), len(token.string) - len(trimmed)))
788
789 # This check is also orthogonal since it is based on metadata.
790 if token.metadata.is_implied_semicolon:
791 self._HandleError(errors.MISSING_SEMICOLON,
792 'Missing semicolon at end of line', token)
793
794 def _HandleStartBracket(self, token, last_non_space_token):
795 """Handles a token that is an open bracket.
796
797 Args:
798 token: The token to handle.
799 last_non_space_token: The last token that was not a space.
800 """
801 if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and
802 last_non_space_token and
803 last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES):
804 self._HandleError(
805 errors.EXTRA_SPACE, 'Extra space before "["',
806 token.previous, position=Position.All(token.previous.string))
807 # If the [ token is the first token in a line we shouldn't complain
808 # about a missing space before [. This is because some Ecma script
809 # languages allow syntax like:
810 # [Annotation]
811 # class MyClass {...}
812 # So we don't want to blindly warn about missing spaces before [.
813 # In the the future, when rules for computing exactly how many spaces
814 # lines should be indented are added, then we can return errors for
815 # [ tokens that are improperly indented.
816 # For example:
817 # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName =
818 # [a,b,c];
819 # should trigger a proper indentation warning message as [ is not indented
820 # by four spaces.
821 elif (not token.IsFirstInLine() and token.previous and
822 token.previous.type not in (
823 [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] +
824 Type.EXPRESSION_ENDER_TYPES)):
825 self._HandleError(errors.MISSING_SPACE, 'Missing space before "["',
826 token, position=Position.AtBeginning())
827
828 def Finalize(self, state):
829 """Perform all checks that need to occur after all lines are processed.
830
831 Args:
832 state: State of the parser after parsing all tokens
833
834 Raises:
835 TypeError: If not overridden.
836 """
837 last_non_space_token = state.GetLastNonSpaceToken()
838 # Check last line for ending with newline.
839 if state.GetLastLine() and not (
840 state.GetLastLine().isspace() or
841 state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()):
842 self._HandleError(
843 errors.FILE_MISSING_NEWLINE,
844 'File does not end with new line. (%s)' % state.GetLastLine(),
845 last_non_space_token)
846
847 try:
848 self._indentation.Finalize()
849 except Exception, e:
850 self._HandleError(
851 errors.FILE_DOES_NOT_PARSE,
852 str(e),
853 last_non_space_token)
854
855 def GetLongLineExceptions(self):
856 """Gets a list of regexps for lines which can be longer than the limit.
857
858 Returns:
859 A list of regexps, used as matches (rather than searches).
860 """
861 return []
862
863 def InExplicitlyTypedLanguage(self):
864 """Returns whether this ecma implementation is explicitly typed."""
865 return False
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698