1#!/usr/bin/env python 2# Copyright 2011 The Closure Linter Authors. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS-IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Methods for checking JS files for common style guide violations. 17 18These style guide violations should only apply to JavaScript and not an Ecma 19scripting languages. 20""" 21 22__author__ = ('robbyw@google.com (Robert Walker)', 23 'ajp@google.com (Andy Perelson)', 24 'jacobr@google.com (Jacob Richman)') 25 26import re 27 28from closure_linter import ecmalintrules 29from closure_linter import error_check 30from closure_linter import errors 31from closure_linter import javascripttokenizer 32from closure_linter import javascripttokens 33from closure_linter import requireprovidesorter 34from closure_linter import tokenutil 35from closure_linter.common import error 36from closure_linter.common import position 37 38# Shorthand 39Error = error.Error 40Position = position.Position 41Rule = error_check.Rule 42Type = javascripttokens.JavaScriptTokenType 43 44 45class JavaScriptLintRules(ecmalintrules.EcmaScriptLintRules): 46 """JavaScript lint rules that catch JavaScript specific style errors.""" 47 48 def __init__(self, namespaces_info): 49 """Initializes a JavaScriptLintRules instance.""" 50 ecmalintrules.EcmaScriptLintRules.__init__(self) 51 self._namespaces_info = namespaces_info 52 self._declared_private_member_tokens = {} 53 self._declared_private_members = set() 54 self._used_private_members = set() 55 # A stack of dictionaries, one for each function scope entered. Each 56 # dictionary is keyed by an identifier that defines a local variable and has 57 # a token as its value. 58 self._unused_local_variables_by_scope = [] 59 60 def HandleMissingParameterDoc(self, token, param_name): 61 """Handle errors associated with a parameter missing a param tag.""" 62 self._HandleError(errors.MISSING_PARAMETER_DOCUMENTATION, 63 'Missing docs for parameter: "%s"' % param_name, token) 64 65 # pylint: disable=too-many-statements 66 def CheckToken(self, token, state): 67 """Checks a token, given the current parser_state, for warnings and errors. 68 69 Args: 70 token: The current token under consideration 71 state: parser_state object that indicates the current state in the page 72 """ 73 74 # Call the base class's CheckToken function. 75 super(JavaScriptLintRules, self).CheckToken(token, state) 76 77 # Store some convenience variables 78 namespaces_info = self._namespaces_info 79 80 if error_check.ShouldCheck(Rule.UNUSED_LOCAL_VARIABLES): 81 self._CheckUnusedLocalVariables(token, state) 82 83 if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS): 84 # Find all assignments to private members. 85 if token.type == Type.SIMPLE_LVALUE: 86 identifier = token.string 87 if identifier.endswith('_') and not identifier.endswith('__'): 88 doc_comment = state.GetDocComment() 89 suppressed = doc_comment and ( 90 'underscore' in doc_comment.suppressions or 91 'unusedPrivateMembers' in doc_comment.suppressions) 92 if not suppressed: 93 # Look for static members defined on a provided namespace. 94 if namespaces_info: 95 namespace = namespaces_info.GetClosurizedNamespace(identifier) 96 provided_namespaces = namespaces_info.GetProvidedNamespaces() 97 else: 98 namespace = None 99 provided_namespaces = set() 100 101 # Skip cases of this.something_.somethingElse_. 102 regex = re.compile(r'^this\.[a-zA-Z_]+$') 103 if namespace in provided_namespaces or regex.match(identifier): 104 variable = identifier.split('.')[-1] 105 self._declared_private_member_tokens[variable] = token 106 self._declared_private_members.add(variable) 107 elif not identifier.endswith('__'): 108 # Consider setting public members of private members to be a usage. 109 for piece in identifier.split('.'): 110 if piece.endswith('_'): 111 self._used_private_members.add(piece) 112 113 # Find all usages of private members. 114 if token.type == Type.IDENTIFIER: 115 for piece in token.string.split('.'): 116 if piece.endswith('_'): 117 self._used_private_members.add(piece) 118 119 if token.type == Type.DOC_FLAG: 120 flag = token.attached_object 121 122 if flag.flag_type == 'param' and flag.name_token is not None: 123 self._CheckForMissingSpaceBeforeToken( 124 token.attached_object.name_token) 125 126 if flag.type is not None and flag.name is not None: 127 if error_check.ShouldCheck(Rule.VARIABLE_ARG_MARKER): 128 # Check for variable arguments marker in type. 129 if flag.jstype.IsVarArgsType() and flag.name != 'var_args': 130 self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_NAME, 131 'Variable length argument %s must be renamed ' 132 'to var_args.' % flag.name, 133 token) 134 elif not flag.jstype.IsVarArgsType() and flag.name == 'var_args': 135 self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_TYPE, 136 'Variable length argument %s type must start ' 137 'with \'...\'.' % flag.name, 138 token) 139 140 if error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER): 141 # Check for optional marker in type. 142 if (flag.jstype.opt_arg and 143 not flag.name.startswith('opt_')): 144 self._HandleError(errors.JSDOC_MISSING_OPTIONAL_PREFIX, 145 'Optional parameter name %s must be prefixed ' 146 'with opt_.' % flag.name, 147 token) 148 elif (not flag.jstype.opt_arg and 149 flag.name.startswith('opt_')): 150 self._HandleError(errors.JSDOC_MISSING_OPTIONAL_TYPE, 151 'Optional parameter %s type must end with =.' % 152 flag.name, 153 token) 154 155 if flag.flag_type in state.GetDocFlag().HAS_TYPE: 156 # Check for both missing type token and empty type braces '{}' 157 # Missing suppress types are reported separately and we allow enums, 158 # const, private, public and protected without types. 159 if (flag.flag_type not in state.GetDocFlag().CAN_OMIT_TYPE 160 and (not flag.jstype or flag.jstype.IsEmpty())): 161 self._HandleError(errors.MISSING_JSDOC_TAG_TYPE, 162 'Missing type in %s tag' % token.string, token) 163 164 elif flag.name_token and flag.type_end_token and tokenutil.Compare( 165 flag.type_end_token, flag.name_token) > 0: 166 self._HandleError( 167 errors.OUT_OF_ORDER_JSDOC_TAG_TYPE, 168 'Type should be immediately after %s tag' % token.string, 169 token) 170 171 elif token.type == Type.DOUBLE_QUOTE_STRING_START: 172 next_token = token.next 173 while next_token.type == Type.STRING_TEXT: 174 if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search( 175 next_token.string): 176 break 177 next_token = next_token.next 178 else: 179 self._HandleError( 180 errors.UNNECESSARY_DOUBLE_QUOTED_STRING, 181 'Single-quoted string preferred over double-quoted string.', 182 token, 183 position=Position.All(token.string)) 184 185 elif token.type == Type.END_DOC_COMMENT: 186 doc_comment = state.GetDocComment() 187 188 # When @externs appears in a @fileoverview comment, it should trigger 189 # the same limited doc checks as a special filename like externs.js. 190 if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag('externs'): 191 self._SetLimitedDocChecks(True) 192 193 if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL) and 194 not self._is_html and 195 state.InTopLevel() and 196 not state.InNonScopeBlock()): 197 198 # Check if we're in a fileoverview or constructor JsDoc. 199 is_constructor = ( 200 doc_comment.HasFlag('constructor') or 201 doc_comment.HasFlag('interface')) 202 # @fileoverview is an optional tag so if the dosctring is the first 203 # token in the file treat it as a file level docstring. 204 is_file_level_comment = ( 205 doc_comment.HasFlag('fileoverview') or 206 not doc_comment.start_token.previous) 207 208 # If the comment is not a file overview, and it does not immediately 209 # precede some code, skip it. 210 # NOTE: The tokenutil methods are not used here because of their 211 # behavior at the top of a file. 212 next_token = token.next 213 if (not next_token or 214 (not is_file_level_comment and 215 next_token.type in Type.NON_CODE_TYPES)): 216 return 217 218 # Don't require extra blank lines around suppression of extra 219 # goog.require errors. 220 if (doc_comment.SuppressionOnly() and 221 next_token.type == Type.IDENTIFIER and 222 next_token.string in ['goog.provide', 'goog.require']): 223 return 224 225 # Find the start of this block (include comments above the block, unless 226 # this is a file overview). 227 block_start = doc_comment.start_token 228 if not is_file_level_comment: 229 token = block_start.previous 230 while token and token.type in Type.COMMENT_TYPES: 231 block_start = token 232 token = token.previous 233 234 # Count the number of blank lines before this block. 235 blank_lines = 0 236 token = block_start.previous 237 while token and token.type in [Type.WHITESPACE, Type.BLANK_LINE]: 238 if token.type == Type.BLANK_LINE: 239 # A blank line. 240 blank_lines += 1 241 elif token.type == Type.WHITESPACE and not token.line.strip(): 242 # A line with only whitespace on it. 243 blank_lines += 1 244 token = token.previous 245 246 # Log errors. 247 error_message = False 248 expected_blank_lines = 0 249 250 # Only need blank line before file overview if it is not the beginning 251 # of the file, e.g. copyright is first. 252 if is_file_level_comment and blank_lines == 0 and block_start.previous: 253 error_message = 'Should have a blank line before a file overview.' 254 expected_blank_lines = 1 255 elif is_constructor and blank_lines != 3: 256 error_message = ( 257 'Should have 3 blank lines before a constructor/interface.') 258 expected_blank_lines = 3 259 elif (not is_file_level_comment and not is_constructor and 260 blank_lines != 2): 261 error_message = 'Should have 2 blank lines between top-level blocks.' 262 expected_blank_lines = 2 263 264 if error_message: 265 self._HandleError( 266 errors.WRONG_BLANK_LINE_COUNT, error_message, 267 block_start, position=Position.AtBeginning(), 268 fix_data=expected_blank_lines - blank_lines) 269 270 elif token.type == Type.END_BLOCK: 271 if state.InFunction() and state.IsFunctionClose(): 272 is_immediately_called = (token.next and 273 token.next.type == Type.START_PAREN) 274 275 function = state.GetFunction() 276 if not self._limited_doc_checks: 277 if (function.has_return and function.doc and 278 not is_immediately_called and 279 not function.doc.HasFlag('return') and 280 not function.doc.InheritsDocumentation() and 281 not function.doc.HasFlag('constructor')): 282 # Check for proper documentation of return value. 283 self._HandleError( 284 errors.MISSING_RETURN_DOCUMENTATION, 285 'Missing @return JsDoc in function with non-trivial return', 286 function.doc.end_token, position=Position.AtBeginning()) 287 elif (not function.has_return and 288 not function.has_throw and 289 function.doc and 290 function.doc.HasFlag('return') and 291 not state.InInterfaceMethod()): 292 flag = function.doc.GetFlag('return') 293 valid_no_return_names = ['undefined', 'void', '*'] 294 invalid_return = flag.jstype is None or not any( 295 sub_type.identifier in valid_no_return_names 296 for sub_type in flag.jstype.IterTypeGroup()) 297 298 if invalid_return: 299 self._HandleError( 300 errors.UNNECESSARY_RETURN_DOCUMENTATION, 301 'Found @return JsDoc on function that returns nothing', 302 flag.flag_token, position=Position.AtBeginning()) 303 304 # b/4073735. Method in object literal definition of prototype can 305 # safely reference 'this'. 306 prototype_object_literal = False 307 block_start = None 308 previous_code = None 309 previous_previous_code = None 310 311 # Search for cases where prototype is defined as object literal. 312 # previous_previous_code 313 # | previous_code 314 # | | block_start 315 # | | | 316 # a.b.prototype = { 317 # c : function() { 318 # this.d = 1; 319 # } 320 # } 321 322 # If in object literal, find first token of block so to find previous 323 # tokens to check above condition. 324 if state.InObjectLiteral(): 325 block_start = state.GetCurrentBlockStart() 326 327 # If an object literal then get previous token (code type). For above 328 # case it should be '='. 329 if block_start: 330 previous_code = tokenutil.SearchExcept(block_start, 331 Type.NON_CODE_TYPES, 332 reverse=True) 333 334 # If previous token to block is '=' then get its previous token. 335 if previous_code and previous_code.IsOperator('='): 336 previous_previous_code = tokenutil.SearchExcept(previous_code, 337 Type.NON_CODE_TYPES, 338 reverse=True) 339 340 # If variable/token before '=' ends with '.prototype' then its above 341 # case of prototype defined with object literal. 342 prototype_object_literal = (previous_previous_code and 343 previous_previous_code.string.endswith( 344 '.prototype')) 345 346 if (function.has_this and function.doc and 347 not function.doc.HasFlag('this') and 348 not function.is_constructor and 349 not function.is_interface and 350 '.prototype.' not in function.name and 351 not prototype_object_literal): 352 self._HandleError( 353 errors.MISSING_JSDOC_TAG_THIS, 354 'Missing @this JsDoc in function referencing "this". (' 355 'this usually means you are trying to reference "this" in ' 356 'a static function, or you have forgotten to mark a ' 357 'constructor with @constructor)', 358 function.doc.end_token, position=Position.AtBeginning()) 359 360 elif token.type == Type.IDENTIFIER: 361 if token.string == 'goog.inherits' and not state.InFunction(): 362 if state.GetLastNonSpaceToken().line_number == token.line_number: 363 self._HandleError( 364 errors.MISSING_LINE, 365 'Missing newline between constructor and goog.inherits', 366 token, 367 position=Position.AtBeginning()) 368 369 extra_space = state.GetLastNonSpaceToken().next 370 while extra_space != token: 371 if extra_space.type == Type.BLANK_LINE: 372 self._HandleError( 373 errors.EXTRA_LINE, 374 'Extra line between constructor and goog.inherits', 375 extra_space) 376 extra_space = extra_space.next 377 378 # TODO(robbyw): Test the last function was a constructor. 379 # TODO(robbyw): Test correct @extends and @implements documentation. 380 381 elif (token.string == 'goog.provide' and 382 not state.InFunction() and 383 namespaces_info is not None): 384 namespace = tokenutil.GetStringAfterToken(token) 385 386 # Report extra goog.provide statement. 387 if not namespace or namespaces_info.IsExtraProvide(token): 388 if not namespace: 389 msg = 'Empty namespace in goog.provide' 390 else: 391 msg = 'Unnecessary goog.provide: ' + namespace 392 393 # Hint to user if this is a Test namespace. 394 if namespace.endswith('Test'): 395 msg += (' *Test namespaces must be mentioned in the ' 396 'goog.setTestOnly() call') 397 398 self._HandleError( 399 errors.EXTRA_GOOG_PROVIDE, 400 msg, 401 token, position=Position.AtBeginning()) 402 403 if namespaces_info.IsLastProvide(token): 404 # Report missing provide statements after the last existing provide. 405 missing_provides = namespaces_info.GetMissingProvides() 406 if missing_provides: 407 self._ReportMissingProvides( 408 missing_provides, 409 tokenutil.GetLastTokenInSameLine(token).next, 410 False) 411 412 # If there are no require statements, missing requires should be 413 # reported after the last provide. 414 if not namespaces_info.GetRequiredNamespaces(): 415 missing_requires, illegal_alias_statements = ( 416 namespaces_info.GetMissingRequires()) 417 if missing_requires: 418 self._ReportMissingRequires( 419 missing_requires, 420 tokenutil.GetLastTokenInSameLine(token).next, 421 True) 422 if illegal_alias_statements: 423 self._ReportIllegalAliasStatement(illegal_alias_statements) 424 425 elif (token.string == 'goog.require' and 426 not state.InFunction() and 427 namespaces_info is not None): 428 namespace = tokenutil.GetStringAfterToken(token) 429 430 # If there are no provide statements, missing provides should be 431 # reported before the first require. 432 if (namespaces_info.IsFirstRequire(token) and 433 not namespaces_info.GetProvidedNamespaces()): 434 missing_provides = namespaces_info.GetMissingProvides() 435 if missing_provides: 436 self._ReportMissingProvides( 437 missing_provides, 438 tokenutil.GetFirstTokenInSameLine(token), 439 True) 440 441 # Report extra goog.require statement. 442 if not namespace or namespaces_info.IsExtraRequire(token): 443 if not namespace: 444 msg = 'Empty namespace in goog.require' 445 else: 446 msg = 'Unnecessary goog.require: ' + namespace 447 448 self._HandleError( 449 errors.EXTRA_GOOG_REQUIRE, 450 msg, 451 token, position=Position.AtBeginning()) 452 453 # Report missing goog.require statements. 454 if namespaces_info.IsLastRequire(token): 455 missing_requires, illegal_alias_statements = ( 456 namespaces_info.GetMissingRequires()) 457 if missing_requires: 458 self._ReportMissingRequires( 459 missing_requires, 460 tokenutil.GetLastTokenInSameLine(token).next, 461 False) 462 if illegal_alias_statements: 463 self._ReportIllegalAliasStatement(illegal_alias_statements) 464 465 elif token.type == Type.OPERATOR: 466 last_in_line = token.IsLastInLine() 467 # If the token is unary and appears to be used in a unary context 468 # it's ok. Otherwise, if it's at the end of the line or immediately 469 # before a comment, it's ok. 470 # Don't report an error before a start bracket - it will be reported 471 # by that token's space checks. 472 if (not token.metadata.IsUnaryOperator() and not last_in_line 473 and not token.next.IsComment() 474 and not token.next.IsOperator(',') 475 and not tokenutil.IsDot(token) 476 and token.next.type not in (Type.WHITESPACE, Type.END_PAREN, 477 Type.END_BRACKET, Type.SEMICOLON, 478 Type.START_BRACKET)): 479 self._HandleError( 480 errors.MISSING_SPACE, 481 'Missing space after "%s"' % token.string, 482 token, 483 position=Position.AtEnd(token.string)) 484 elif token.type == Type.WHITESPACE: 485 first_in_line = token.IsFirstInLine() 486 last_in_line = token.IsLastInLine() 487 # Check whitespace length if it's not the first token of the line and 488 # if it's not immediately before a comment. 489 if not last_in_line and not first_in_line and not token.next.IsComment(): 490 # Ensure there is no space after opening parentheses. 491 if (token.previous.type in (Type.START_PAREN, Type.START_BRACKET, 492 Type.FUNCTION_NAME) 493 or token.next.type == Type.START_PARAMETERS): 494 self._HandleError( 495 errors.EXTRA_SPACE, 496 'Extra space after "%s"' % token.previous.string, 497 token, 498 position=Position.All(token.string)) 499 elif token.type == Type.SEMICOLON: 500 previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, 501 reverse=True) 502 if not previous_token: 503 self._HandleError( 504 errors.REDUNDANT_SEMICOLON, 505 'Semicolon without any statement', 506 token, 507 position=Position.AtEnd(token.string)) 508 elif (previous_token.type == Type.KEYWORD and 509 previous_token.string not in ['break', 'continue', 'return']): 510 self._HandleError( 511 errors.REDUNDANT_SEMICOLON, 512 ('Semicolon after \'%s\' without any statement.' 513 ' Looks like an error.' % previous_token.string), 514 token, 515 position=Position.AtEnd(token.string)) 516 517 def _CheckUnusedLocalVariables(self, token, state): 518 """Checks for unused local variables in function blocks. 519 520 Args: 521 token: The token to check. 522 state: The state tracker. 523 """ 524 # We don't use state.InFunction because that disregards scope functions. 525 in_function = state.FunctionDepth() > 0 526 if token.type == Type.SIMPLE_LVALUE or token.type == Type.IDENTIFIER: 527 if in_function: 528 identifier = token.string 529 # Check whether the previous token was var. 530 previous_code_token = tokenutil.CustomSearch( 531 token, 532 lambda t: t.type not in Type.NON_CODE_TYPES, 533 reverse=True) 534 if previous_code_token and previous_code_token.IsKeyword('var'): 535 # Add local variable declaration to the top of the unused locals 536 # stack. 537 self._unused_local_variables_by_scope[-1][identifier] = token 538 elif token.type == Type.IDENTIFIER: 539 # This covers most cases where the variable is used as an identifier. 540 self._MarkLocalVariableUsed(token.string) 541 elif token.type == Type.SIMPLE_LVALUE and '.' in identifier: 542 # This covers cases where a value is assigned to a property of the 543 # variable. 544 self._MarkLocalVariableUsed(token.string) 545 elif token.type == Type.START_BLOCK: 546 if in_function and state.IsFunctionOpen(): 547 # Push a new map onto the stack 548 self._unused_local_variables_by_scope.append({}) 549 elif token.type == Type.END_BLOCK: 550 if state.IsFunctionClose(): 551 # Pop the stack and report any remaining locals as unused. 552 unused_local_variables = self._unused_local_variables_by_scope.pop() 553 for unused_token in unused_local_variables.values(): 554 self._HandleError( 555 errors.UNUSED_LOCAL_VARIABLE, 556 'Unused local variable: %s.' % unused_token.string, 557 unused_token) 558 elif token.type == Type.DOC_FLAG: 559 # Flags that use aliased symbols should be counted. 560 flag = token.attached_object 561 js_type = flag and flag.jstype 562 if flag and flag.flag_type in state.GetDocFlag().HAS_TYPE and js_type: 563 self._MarkAliasUsed(js_type) 564 565 def _MarkAliasUsed(self, js_type): 566 """Marks aliases in a type as used. 567 568 Recursively iterates over all subtypes in a jsdoc type annotation and 569 tracks usage of aliased symbols (which may be local variables). 570 Marks the local variable as used in the scope nearest to the current 571 scope that matches the given token. 572 573 Args: 574 js_type: The jsdoc type, a typeannotation.TypeAnnotation object. 575 """ 576 if js_type.alias: 577 self._MarkLocalVariableUsed(js_type.identifier) 578 for sub_type in js_type.IterTypes(): 579 self._MarkAliasUsed(sub_type) 580 581 def _MarkLocalVariableUsed(self, identifier): 582 """Marks the local variable as used in the relevant scope. 583 584 Marks the local variable in the scope nearest to the current scope that 585 matches the given identifier as used. 586 587 Args: 588 identifier: The identifier representing the potential usage of a local 589 variable. 590 """ 591 identifier = identifier.split('.', 1)[0] 592 # Find the first instance of the identifier in the stack of function scopes 593 # and mark it used. 594 for unused_local_variables in reversed( 595 self._unused_local_variables_by_scope): 596 if identifier in unused_local_variables: 597 del unused_local_variables[identifier] 598 break 599 600 def _ReportMissingProvides(self, missing_provides, token, need_blank_line): 601 """Reports missing provide statements to the error handler. 602 603 Args: 604 missing_provides: A dictionary of string(key) and integer(value) where 605 each string(key) is a namespace that should be provided, but is not 606 and integer(value) is first line number where it's required. 607 token: The token where the error was detected (also where the new provides 608 will be inserted. 609 need_blank_line: Whether a blank line needs to be inserted after the new 610 provides are inserted. May be True, False, or None, where None 611 indicates that the insert location is unknown. 612 """ 613 614 missing_provides_msg = 'Missing the following goog.provide statements:\n' 615 missing_provides_msg += '\n'.join(['goog.provide(\'%s\');' % x for x in 616 sorted(missing_provides)]) 617 missing_provides_msg += '\n' 618 619 missing_provides_msg += '\nFirst line where provided: \n' 620 missing_provides_msg += '\n'.join( 621 [' %s : line %d' % (x, missing_provides[x]) for x in 622 sorted(missing_provides)]) 623 missing_provides_msg += '\n' 624 625 self._HandleError( 626 errors.MISSING_GOOG_PROVIDE, 627 missing_provides_msg, 628 token, position=Position.AtBeginning(), 629 fix_data=(missing_provides.keys(), need_blank_line)) 630 631 def _ReportMissingRequires(self, missing_requires, token, need_blank_line): 632 """Reports missing require statements to the error handler. 633 634 Args: 635 missing_requires: A dictionary of string(key) and integer(value) where 636 each string(key) is a namespace that should be required, but is not 637 and integer(value) is first line number where it's required. 638 token: The token where the error was detected (also where the new requires 639 will be inserted. 640 need_blank_line: Whether a blank line needs to be inserted before the new 641 requires are inserted. May be True, False, or None, where None 642 indicates that the insert location is unknown. 643 """ 644 645 missing_requires_msg = 'Missing the following goog.require statements:\n' 646 missing_requires_msg += '\n'.join(['goog.require(\'%s\');' % x for x in 647 sorted(missing_requires)]) 648 missing_requires_msg += '\n' 649 650 missing_requires_msg += '\nFirst line where required: \n' 651 missing_requires_msg += '\n'.join( 652 [' %s : line %d' % (x, missing_requires[x]) for x in 653 sorted(missing_requires)]) 654 missing_requires_msg += '\n' 655 656 self._HandleError( 657 errors.MISSING_GOOG_REQUIRE, 658 missing_requires_msg, 659 token, position=Position.AtBeginning(), 660 fix_data=(missing_requires.keys(), need_blank_line)) 661 662 def _ReportIllegalAliasStatement(self, illegal_alias_statements): 663 """Reports alias statements that would need a goog.require.""" 664 for namespace, token in illegal_alias_statements.iteritems(): 665 self._HandleError( 666 errors.ALIAS_STMT_NEEDS_GOOG_REQUIRE, 667 'The alias definition would need the namespace \'%s\' which is not ' 668 'required through any other symbol.' % namespace, 669 token, position=Position.AtBeginning()) 670 671 def Finalize(self, state): 672 """Perform all checks that need to occur after all lines are processed.""" 673 # Call the base class's Finalize function. 674 super(JavaScriptLintRules, self).Finalize(state) 675 676 if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS): 677 # Report an error for any declared private member that was never used. 678 unused_private_members = (self._declared_private_members - 679 self._used_private_members) 680 681 for variable in unused_private_members: 682 token = self._declared_private_member_tokens[variable] 683 self._HandleError(errors.UNUSED_PRIVATE_MEMBER, 684 'Unused private member: %s.' % token.string, 685 token) 686 687 # Clear state to prepare for the next file. 688 self._declared_private_member_tokens = {} 689 self._declared_private_members = set() 690 self._used_private_members = set() 691 692 namespaces_info = self._namespaces_info 693 if namespaces_info is not None: 694 # If there are no provide or require statements, missing provides and 695 # requires should be reported on line 1. 696 if (not namespaces_info.GetProvidedNamespaces() and 697 not namespaces_info.GetRequiredNamespaces()): 698 missing_provides = namespaces_info.GetMissingProvides() 699 if missing_provides: 700 self._ReportMissingProvides( 701 missing_provides, state.GetFirstToken(), None) 702 703 missing_requires, illegal_alias = namespaces_info.GetMissingRequires() 704 if missing_requires: 705 self._ReportMissingRequires( 706 missing_requires, state.GetFirstToken(), None) 707 if illegal_alias: 708 self._ReportIllegalAliasStatement(illegal_alias) 709 710 self._CheckSortedRequiresProvides(state.GetFirstToken()) 711 712 def _CheckSortedRequiresProvides(self, token): 713 """Checks that all goog.require and goog.provide statements are sorted. 714 715 Note that this method needs to be run after missing statements are added to 716 preserve alphabetical order. 717 718 Args: 719 token: The first token in the token stream. 720 """ 721 sorter = requireprovidesorter.RequireProvideSorter() 722 first_provide_token = sorter.CheckProvides(token) 723 if first_provide_token: 724 new_order = sorter.GetFixedProvideString(first_provide_token) 725 self._HandleError( 726 errors.GOOG_PROVIDES_NOT_ALPHABETIZED, 727 'goog.provide classes must be alphabetized. The correct code is:\n' + 728 new_order, 729 first_provide_token, 730 position=Position.AtBeginning(), 731 fix_data=first_provide_token) 732 733 first_require_token = sorter.CheckRequires(token) 734 if first_require_token: 735 new_order = sorter.GetFixedRequireString(first_require_token) 736 self._HandleError( 737 errors.GOOG_REQUIRES_NOT_ALPHABETIZED, 738 'goog.require classes must be alphabetized. The correct code is:\n' + 739 new_order, 740 first_require_token, 741 position=Position.AtBeginning(), 742 fix_data=first_require_token) 743 744 def GetLongLineExceptions(self): 745 """Gets a list of regexps for lines which can be longer than the limit. 746 747 Returns: 748 A list of regexps, used as matches (rather than searches). 749 """ 750 return [ 751 re.compile(r'.*// @suppress longLineCheck$'), 752 re.compile(r'((var|let|const) .+\s*=\s*)?goog\.require\(.+\);?\s*$'), 753 re.compile(r'goog\.(forwardDeclare|module|provide|setTestOnly)' 754 r'\(.+\);?\s*$'), 755 re.compile(r'[\s/*]*@visibility\s*{.*}[\s*/]*$'), 756 ] 757