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