Exemple #1
0
  def _ProcessToken(self):
    """Process the given token."""
    token = self._token
    token.metadata = self._CreateMetaData()
    context = (self._ProcessContext() or self._context)
    token.metadata.context = context
    token.metadata.last_code = self._last_code

    # Determine the operator type of the token, if applicable.
    if token.type == TokenType.OPERATOR:
      token.metadata.operator_type = self._GetOperatorType(token)

    # Determine if there is an implied semicolon after the token.
    if token.type != TokenType.SEMICOLON:
      next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
      # A statement like if (x) does not need a semicolon after it
      is_implied_block = self._context == EcmaContext.IMPLIED_BLOCK
      is_last_code_in_line = token.IsCode() and (
          not next_code or next_code.line_number != token.line_number)
      is_continued_identifier = (token.type == TokenType.IDENTIFIER and
                                 token.string.endswith('.'))
      is_continued_operator = (token.type == TokenType.OPERATOR and
                               not token.metadata.IsUnaryPostOperator())
      is_continued_dot = token.string == '.'
      next_code_is_operator = next_code and next_code.type == TokenType.OPERATOR
      next_code_is_dot = next_code and next_code.string == '.'
      is_end_of_block = (
          token.type == TokenType.END_BLOCK and
          token.metadata.context.type != EcmaContext.OBJECT_LITERAL)
      is_multiline_string = token.type == TokenType.STRING_TEXT
      is_continued_var_decl = (token.IsKeyword('var') and
                               next_code and
                               (next_code.type in [TokenType.IDENTIFIER,
                                                   TokenType.SIMPLE_LVALUE]) and
                               token.line_number < next_code.line_number)
      next_code_is_block = next_code and next_code.type == TokenType.START_BLOCK
      if (is_last_code_in_line and
          self._StatementCouldEndInContext() and
          not is_multiline_string and
          not is_end_of_block and
          not is_continued_var_decl and
          not is_continued_identifier and
          not is_continued_operator and
          not is_continued_dot and
          not next_code_is_dot and
          not next_code_is_operator and
          not is_implied_block and
          not next_code_is_block):
        token.metadata.is_implied_semicolon = True
        self._EndStatement()
  def GetBlockType(self, token):
    """Determine the block type given a START_BLOCK token.

    Code blocks come after parameters, keywords  like else, and closing parens.

    Args:
      token: The current token. Can be assumed to be type START_BLOCK
    Returns:
      Code block type for current token.
    """
    last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, reverse=True)
    if last_code.type in (Type.END_PARAMETERS, Type.END_PAREN,
                          Type.KEYWORD) and not last_code.IsKeyword('return'):
      return self.CODE
    else:
      return self.OBJECT_LITERAL
    def CheckToken(self, token, state):
        """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration
      state: parser_state object that indicates the current state in the page
    """

        # For @param don't ignore record type.
        if (self.__ContainsRecordType(token)
                and not token.attached_object.flag_type == 'param'):
            # We should bail out and not emit any warnings for this annotation.
            # TODO(nicksantos): Support record types for real.
            state.GetDocComment().Invalidate()
            return

        # Call the base class's CheckToken function.
        super(JavaScriptLintRules, self).CheckToken(token, state)

        # Store some convenience variables
        namespaces_info = self._namespaces_info

        if error_check.ShouldCheck(Rule.UNUSED_LOCAL_VARIABLES):
            self._CheckUnusedLocalVariables(token, state)

        if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
            # Find all assignments to private members.
            if token.type == Type.SIMPLE_LVALUE:
                identifier = token.string
                if identifier.endswith('_') and not identifier.endswith('__'):
                    doc_comment = state.GetDocComment()
                    suppressed = (doc_comment
                                  and doc_comment.HasFlag('suppress')
                                  and doc_comment.GetFlag('suppress').type
                                  == 'underscore')
                    if not suppressed:
                        # Look for static members defined on a provided namespace.
                        if namespaces_info:
                            namespace = namespaces_info.GetClosurizedNamespace(
                                identifier)
                            provided_namespaces = namespaces_info.GetProvidedNamespaces(
                            )
                        else:
                            namespace = None
                            provided_namespaces = set()

                        # Skip cases of this.something_.somethingElse_.
                        regex = re.compile('^this\.[a-zA-Z_]+$')
                        if namespace in provided_namespaces or regex.match(
                                identifier):
                            variable = identifier.split('.')[-1]
                            self._declared_private_member_tokens[
                                variable] = token
                            self._declared_private_members.add(variable)
                elif not identifier.endswith('__'):
                    # Consider setting public members of private members to be a usage.
                    for piece in identifier.split('.'):
                        if piece.endswith('_'):
                            self._used_private_members.add(piece)

            # Find all usages of private members.
            if token.type == Type.IDENTIFIER:
                for piece in token.string.split('.'):
                    if piece.endswith('_'):
                        self._used_private_members.add(piece)

        if token.type == Type.DOC_FLAG:
            flag = token.attached_object

            if flag.flag_type == 'param' and flag.name_token is not None:
                self._CheckForMissingSpaceBeforeToken(
                    token.attached_object.name_token)

                if flag.type is not None and flag.name is not None:
                    if error_check.ShouldCheck(Rule.VARIABLE_ARG_MARKER):
                        # Check for variable arguments marker in type.
                        if (flag.type.startswith('...')
                                and not flag.name == 'var_args'):
                            self._HandleError(
                                errors.JSDOC_MISSING_VAR_ARGS_NAME,
                                'Variable length argument %s must be renamed '
                                'to var_args.' % flag.name, token)
                        elif (not flag.type.startswith('...')
                              and flag.name == 'var_args'):
                            self._HandleError(
                                errors.JSDOC_MISSING_VAR_ARGS_TYPE,
                                'Variable length argument %s type must start '
                                'with \'...\'.' % flag.name, token)

                    if error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER):
                        # Check for optional marker in type.
                        if (flag.type.endswith('=')
                                and not flag.name.startswith('opt_')):
                            self._HandleError(
                                errors.JSDOC_MISSING_OPTIONAL_PREFIX,
                                'Optional parameter name %s must be prefixed '
                                'with opt_.' % flag.name, token)
                        elif (not flag.type.endswith('=')
                              and flag.name.startswith('opt_')):
                            self._HandleError(
                                errors.JSDOC_MISSING_OPTIONAL_TYPE,
                                'Optional parameter %s type must end with =.' %
                                flag.name, token)

            if flag.flag_type in state.GetDocFlag().HAS_TYPE:
                # Check for both missing type token and empty type braces '{}'
                # Missing suppress types are reported separately and we allow enums
                # without types.
                if (flag.flag_type not in ('suppress', 'enum')
                        and (not flag.type or flag.type.isspace())):
                    self._HandleError(errors.MISSING_JSDOC_TAG_TYPE,
                                      'Missing type in %s tag' % token.string,
                                      token)

                elif flag.name_token and flag.type_end_token and tokenutil.Compare(
                        flag.type_end_token, flag.name_token) > 0:
                    self._HandleError(
                        errors.OUT_OF_ORDER_JSDOC_TAG_TYPE,
                        'Type should be immediately after %s tag' %
                        token.string, token)

        elif token.type == Type.DOUBLE_QUOTE_STRING_START:
            next_token = token.next
            while next_token.type == Type.STRING_TEXT:
                if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
                        next_token.string):
                    break
                next_token = next_token.next
            else:
                self._HandleError(
                    errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
                    'Single-quoted string preferred over double-quoted string.',
                    token, Position.All(token.string))

        elif token.type == Type.END_DOC_COMMENT:
            doc_comment = state.GetDocComment()

            # When @externs appears in a @fileoverview comment, it should trigger
            # the same limited doc checks as a special filename like externs.js.
            if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag(
                    'externs'):
                self._SetLimitedDocChecks(True)

            if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL)
                    and not self._is_html and state.InTopLevel()
                    and not state.InNonScopeBlock()):

                # Check if we're in a fileoverview or constructor JsDoc.
                is_constructor = (doc_comment.HasFlag('constructor')
                                  or doc_comment.HasFlag('interface'))
                is_file_overview = doc_comment.HasFlag('fileoverview')

                # If the comment is not a file overview, and it does not immediately
                # precede some code, skip it.
                # NOTE: The tokenutil methods are not used here because of their
                # behavior at the top of a file.
                next_token = token.next
                if (not next_token
                        or (not is_file_overview
                            and next_token.type in Type.NON_CODE_TYPES)):
                    return

                # Don't require extra blank lines around suppression of extra
                # goog.require errors.
                if (doc_comment.SuppressionOnly()
                        and next_token.type == Type.IDENTIFIER and
                        next_token.string in ['goog.provide', 'goog.require']):
                    return

                # Find the start of this block (include comments above the block, unless
                # this is a file overview).
                block_start = doc_comment.start_token
                if not is_file_overview:
                    token = block_start.previous
                    while token and token.type in Type.COMMENT_TYPES:
                        block_start = token
                        token = token.previous

                # Count the number of blank lines before this block.
                blank_lines = 0
                token = block_start.previous
                while token and token.type in [
                        Type.WHITESPACE, Type.BLANK_LINE
                ]:
                    if token.type == Type.BLANK_LINE:
                        # A blank line.
                        blank_lines += 1
                    elif token.type == Type.WHITESPACE and not token.line.strip(
                    ):
                        # A line with only whitespace on it.
                        blank_lines += 1
                    token = token.previous

                # Log errors.
                error_message = False
                expected_blank_lines = 0

                # Only need blank line before file overview if it is not the beginning
                # of the file, e.g. copyright is first.
                if is_file_overview and blank_lines == 0 and block_start.previous:
                    error_message = 'Should have a blank line before a file overview.'
                    expected_blank_lines = 1
                elif is_constructor and blank_lines != 3:
                    error_message = (
                        'Should have 3 blank lines before a constructor/interface.'
                    )
                    expected_blank_lines = 3
                elif not is_file_overview and not is_constructor and blank_lines != 2:
                    error_message = 'Should have 2 blank lines between top-level blocks.'
                    expected_blank_lines = 2

                if error_message:
                    self._HandleError(errors.WRONG_BLANK_LINE_COUNT,
                                      error_message, block_start,
                                      Position.AtBeginning(),
                                      expected_blank_lines - blank_lines)

        elif token.type == Type.END_BLOCK:
            if state.InFunction() and state.IsFunctionClose():
                is_immediately_called = (token.next and token.next.type
                                         == Type.START_PAREN)

                function = state.GetFunction()
                if not self._limited_doc_checks:
                    if (function.has_return and function.doc
                            and not is_immediately_called
                            and not function.doc.HasFlag('return')
                            and not function.doc.InheritsDocumentation()
                            and not function.doc.HasFlag('constructor')):
                        # Check for proper documentation of return value.
                        self._HandleError(
                            errors.MISSING_RETURN_DOCUMENTATION,
                            'Missing @return JsDoc in function with non-trivial return',
                            function.doc.end_token, Position.AtBeginning())
                    elif (not function.has_return and not function.has_throw
                          and function.doc and function.doc.HasFlag('return')
                          and not state.InInterfaceMethod()):
                        return_flag = function.doc.GetFlag('return')
                        if (return_flag.type is None
                                or ('undefined' not in return_flag.type
                                    and 'void' not in return_flag.type
                                    and '*' not in return_flag.type)):
                            self._HandleError(
                                errors.UNNECESSARY_RETURN_DOCUMENTATION,
                                'Found @return JsDoc on function that returns nothing',
                                return_flag.flag_token, Position.AtBeginning())

                # b/4073735. Method in object literal definition of prototype can
                # safely reference 'this'.
                prototype_object_literal = False
                block_start = None
                previous_code = None
                previous_previous_code = None

                # Search for cases where prototype is defined as object literal.
                #       previous_previous_code
                #       |       previous_code
                #       |       | block_start
                #       |       | |
                # a.b.prototype = {
                #   c : function() {
                #     this.d = 1;
                #   }
                # }

                # If in object literal, find first token of block so to find previous
                # tokens to check above condition.
                if state.InObjectLiteral():
                    block_start = state.GetCurrentBlockStart()

                # If an object literal then get previous token (code type). For above
                # case it should be '='.
                if block_start:
                    previous_code = tokenutil.SearchExcept(
                        block_start, Type.NON_CODE_TYPES, None, True)

                # If previous token to block is '=' then get its previous token.
                if previous_code and previous_code.IsOperator('='):
                    previous_previous_code = tokenutil.SearchExcept(
                        previous_code, Type.NON_CODE_TYPES, None, True)

                # If variable/token before '=' ends with '.prototype' then its above
                # case of prototype defined with object literal.
                prototype_object_literal = (
                    previous_previous_code
                    and previous_previous_code.string.endswith('.prototype'))

                if (function.has_this and function.doc
                        and not function.doc.HasFlag('this')
                        and not function.is_constructor
                        and not function.is_interface
                        and '.prototype.' not in function.name
                        and not prototype_object_literal):
                    self._HandleError(
                        errors.MISSING_JSDOC_TAG_THIS,
                        'Missing @this JsDoc in function referencing "this". ('
                        'this usually means you are trying to reference "this" in '
                        'a static function, or you have forgotten to mark a '
                        'constructor with @constructor)',
                        function.doc.end_token, Position.AtBeginning())

        elif token.type == Type.IDENTIFIER:
            if token.string == 'goog.inherits' and not state.InFunction():
                if state.GetLastNonSpaceToken(
                ).line_number == token.line_number:
                    self._HandleError(
                        errors.MISSING_LINE,
                        'Missing newline between constructor and goog.inherits',
                        token, Position.AtBeginning())

                extra_space = state.GetLastNonSpaceToken().next
                while extra_space != token:
                    if extra_space.type == Type.BLANK_LINE:
                        self._HandleError(
                            errors.EXTRA_LINE,
                            'Extra line between constructor and goog.inherits',
                            extra_space)
                    extra_space = extra_space.next

                # TODO(robbyw): Test the last function was a constructor.
                # TODO(robbyw): Test correct @extends and @implements documentation.

            elif (token.string == 'goog.provide' and not state.InFunction()
                  and namespaces_info is not None):
                namespace = tokenutil.Search(token, Type.STRING_TEXT).string

                # Report extra goog.provide statement.
                if namespaces_info.IsExtraProvide(token):
                    msg = 'Unnecessary goog.provide: ' + namespace

                    # Hint to user if this is a Test namespace.
                    if namespace.endswith('Test'):
                        msg += (' *Test namespaces must be mentioned in the '
                                'goog.setTestOnly() call')

                    self._HandleError(errors.EXTRA_GOOG_PROVIDE,
                                      msg,
                                      token,
                                      position=Position.AtBeginning())

                if namespaces_info.IsLastProvide(token):
                    # Report missing provide statements after the last existing provide.
                    missing_provides = namespaces_info.GetMissingProvides()
                    if missing_provides:
                        self._ReportMissingProvides(
                            missing_provides,
                            tokenutil.GetLastTokenInSameLine(token).next,
                            False)

                    # If there are no require statements, missing requires should be
                    # reported after the last provide.
                    if not namespaces_info.GetRequiredNamespaces():
                        missing_requires = namespaces_info.GetMissingRequires()
                        if missing_requires:
                            self._ReportMissingRequires(
                                missing_requires,
                                tokenutil.GetLastTokenInSameLine(token).next,
                                True)

            elif (token.string == 'goog.require' and not state.InFunction()
                  and namespaces_info is not None):
                namespace = tokenutil.Search(token, Type.STRING_TEXT).string

                # If there are no provide statements, missing provides should be
                # reported before the first require.
                if (namespaces_info.IsFirstRequire(token)
                        and not namespaces_info.GetProvidedNamespaces()):
                    missing_provides = namespaces_info.GetMissingProvides()
                    if missing_provides:
                        self._ReportMissingProvides(
                            missing_provides,
                            tokenutil.GetFirstTokenInSameLine(token), True)

                # Report extra goog.require statement.
                if namespaces_info.IsExtraRequire(token):

                    self._HandleError(errors.EXTRA_GOOG_REQUIRE,
                                      'Unnecessary goog.require: ' + namespace,
                                      token,
                                      position=Position.AtBeginning())

                # Report missing goog.require statements.
                if namespaces_info.IsLastRequire(token):
                    missing_requires = namespaces_info.GetMissingRequires()
                    if missing_requires:
                        self._ReportMissingRequires(
                            missing_requires,
                            tokenutil.GetLastTokenInSameLine(token).next,
                            False)

        elif token.type == Type.OPERATOR:
            last_in_line = token.IsLastInLine()
            # If the token is unary and appears to be used in a unary context
            # it's ok.  Otherwise, if it's at the end of the line or immediately
            # before a comment, it's ok.
            # Don't report an error before a start bracket - it will be reported
            # by that token's space checks.
            if (not token.metadata.IsUnaryOperator() and not last_in_line
                    and not token.next.IsComment()
                    and not token.next.IsOperator(',') and not token.next.type
                    in (Type.WHITESPACE, Type.END_PAREN, Type.END_BRACKET,
                        Type.SEMICOLON, Type.START_BRACKET)):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space after "%s"' % token.string,
                                  token, Position.AtEnd(token.string))
        elif token.type == Type.WHITESPACE:
            first_in_line = token.IsFirstInLine()
            last_in_line = token.IsLastInLine()
            # Check whitespace length if it's not the first token of the line and
            # if it's not immediately before a comment.
            if not last_in_line and not first_in_line and not token.next.IsComment(
            ):
                # Ensure there is no space after opening parentheses.
                if (token.previous.type
                        in (Type.START_PAREN, Type.START_BRACKET,
                            Type.FUNCTION_NAME)
                        or token.next.type == Type.START_PARAMETERS):
                    self._HandleError(
                        errors.EXTRA_SPACE,
                        'Extra space after "%s"' % token.previous.string,
                        token, Position.All(token.string))
        elif token.type == Type.SEMICOLON:
            previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
                                                    None, True)
            if (previous_token.type == Type.KEYWORD
                    and not previous_token.string
                    in ['break', 'continue', 'return']):
                self._HandleError(
                    errors.REDUNDANT_SEMICOLON,
                    ('Semicolon after \'%s\' without any statement.'
                     ' Looks like an error.' % previous_token.string), token,
                    Position.AtEnd(token.string))
Exemple #4
0
  def CheckToken(self, token, state):
    """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration
      state: parser_state object that indicates the current state in the page
    """
    # Store some convenience variables
    first_in_line = token.IsFirstInLine()
    last_in_line = token.IsLastInLine()
    last_non_space_token = state.GetLastNonSpaceToken()

    token_type = token.type

    # Process the line change.
    if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION):
      # TODO(robbyw): Support checking indentation in HTML files.
      indentation_errors = self._indentation.CheckToken(token, state)
      for indentation_error in indentation_errors:
        self._HandleError(*indentation_error)

    if last_in_line:
      self._CheckLineLength(token, state)

    if token_type == Type.PARAMETERS:
      # Find missing spaces in parameter lists.
      if self.MISSING_PARAMETER_SPACE.search(token.string):
        fix_data = ', '.join([s.strip() for s in token.string.split(',')])
        self._HandleError(errors.MISSING_SPACE, 'Missing space after ","',
                          token, position=None, fix_data=fix_data.strip())

      # Find extra spaces at the beginning of parameter lists.  Make sure
      # we aren't at the beginning of a continuing multi-line list.
      if not first_in_line:
        space_count = len(token.string) - len(token.string.lstrip())
        if space_count:
          self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("',
                            token, position=Position(0, space_count))

    elif (token_type == Type.START_BLOCK and
          token.metadata.context.type == Context.BLOCK):
      self._CheckForMissingSpaceBeforeToken(token)

    elif token_type == Type.END_BLOCK:
      last_code = token.metadata.last_code

      if FLAGS.check_trailing_comma:
        if last_code.IsOperator(','):
          self._HandleError(
              errors.COMMA_AT_END_OF_LITERAL,
              'Illegal comma at end of object literal', last_code,
              position=Position.All(last_code.string))

      if state.InFunction() and state.IsFunctionClose():
        if state.InTopLevelFunction():
          # A semicolons should not be included at the end of a function
          # declaration.
          if not state.InAssignedFunction():
            if not last_in_line and token.next.type == Type.SEMICOLON:
              self._HandleError(
                  errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
                  'Illegal semicolon after function declaration',
                  token.next, position=Position.All(token.next.string))

        # A semicolon should be included at the end of a function expression
        # that is not immediately called or used by a dot operator.
        if (state.InAssignedFunction() and token.next
            and token.next.type != Type.SEMICOLON):
          next_token = tokenutil.GetNextCodeToken(token)
          is_immediately_used = next_token and (
              next_token.type == Type.START_PAREN or
              tokenutil.IsDot(next_token))
          if not is_immediately_used:
            self._HandleError(
                errors.MISSING_SEMICOLON_AFTER_FUNCTION,
                'Missing semicolon after function assigned to a variable',
                token, position=Position.AtEnd(token.string))

        if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK:
          self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
                            'Interface methods cannot contain code', last_code)

      elif (state.IsBlockClose() and
            token.next and token.next.type == Type.SEMICOLON):
        if (last_code.metadata.context.parent.type != Context.OBJECT_LITERAL
            and last_code.metadata.context.type != Context.OBJECT_LITERAL):
          self._HandleError(
              errors.REDUNDANT_SEMICOLON,
              'No semicolon is required to end a code block',
              token.next, position=Position.All(token.next.string))

    elif token_type == Type.SEMICOLON:
      if token.previous and token.previous.type == Type.WHITESPACE:
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space before ";"',
            token.previous, position=Position.All(token.previous.string))

      if token.next and token.next.line_number == token.line_number:
        if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
          # TODO(robbyw): Error about no multi-statement lines.
          pass

        elif token.next.type not in (
            Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN):
          self._HandleError(
              errors.MISSING_SPACE,
              'Missing space after ";" in for statement',
              token.next,
              position=Position.AtBeginning())

      last_code = token.metadata.last_code
      if last_code and last_code.type == Type.SEMICOLON:
        # Allow a single double semi colon in for loops for cases like:
        # for (;;) { }.
        # NOTE(user): This is not a perfect check, and will not throw an error
        # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
        # probably won't work either.
        for_token = tokenutil.CustomSearch(
            last_code,
            lambda token: token.type == Type.KEYWORD and token.string == 'for',
            end_func=lambda token: token.type == Type.SEMICOLON,
            distance=None,
            reverse=True)

        if not for_token:
          self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon',
                            token, position=Position.All(token.string))

    elif token_type == Type.START_PAREN:
      # Ensure that opening parentheses have a space before any keyword
      # that is not being invoked like a member function.
      if (token.previous and token.previous.type == Type.KEYWORD and
          (not token.previous.metadata or
           not token.previous.metadata.last_code or
           not token.previous.metadata.last_code.string or
           token.previous.metadata.last_code.string[-1:] != '.')):
        self._HandleError(errors.MISSING_SPACE, 'Missing space before "("',
                          token, position=Position.AtBeginning())
      elif token.previous and token.previous.type == Type.WHITESPACE:
        before_space = token.previous.previous
        # Ensure that there is no extra space before a function invocation,
        # even if the function being invoked happens to be a keyword.
        if (before_space and before_space.line_number == token.line_number and
            before_space.type == Type.IDENTIFIER or
            (before_space.type == Type.KEYWORD and before_space.metadata and
             before_space.metadata.last_code and
             before_space.metadata.last_code.string and
             before_space.metadata.last_code.string[-1:] == '.')):
          self._HandleError(
              errors.EXTRA_SPACE, 'Extra space before "("',
              token.previous, position=Position.All(token.previous.string))

    elif token_type == Type.START_BRACKET:
      self._HandleStartBracket(token, last_non_space_token)
    elif token_type in (Type.END_PAREN, Type.END_BRACKET):
      # Ensure there is no space before closing parentheses, except when
      # it's in a for statement with an omitted section, or when it's at the
      # beginning of a line.
      last_code = token.metadata.last_code
      if FLAGS.check_trailing_comma and last_code.IsOperator(','):
        if token_type in [Type.END_BRACKET, Type.END_PAREN]:
          self._HandleError(
              errors.COMMA_AT_END_OF_LITERAL,
              'Illegal comma at end of array literal', last_code,
              position=Position.All(last_code.string))

      if (token.previous and token.previous.type == Type.WHITESPACE and
          not token.previous.IsFirstInLine() and
          not (last_non_space_token and last_non_space_token.line_number ==
               token.line_number and
               last_non_space_token.type == Type.SEMICOLON)):
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space before "%s"' %
            token.string, token.previous,
            position=Position.All(token.previous.string))

    elif token_type == Type.WHITESPACE:
      if self.ILLEGAL_TAB.search(token.string):
        if token.IsFirstInLine():
          if token.next:
            self._HandleError(
                errors.ILLEGAL_TAB,
                'Illegal tab in whitespace before "%s"' % token.next.string,
                token, position=Position.All(token.string))
          else:
            self._HandleError(
                errors.ILLEGAL_TAB,
                'Illegal tab in whitespace',
                token, position=Position.All(token.string))
        else:
          self._HandleError(
              errors.ILLEGAL_TAB,
              'Illegal tab in whitespace after "%s"' % token.previous.string,
              token, position=Position.All(token.string))

      # Check whitespace length if it's not the first token of the line and
      # if it's not immediately before a comment.
      if last_in_line:
        # Check for extra whitespace at the end of a line.
        self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line',
                          token, position=Position.All(token.string))
      elif not first_in_line and not token.next.IsComment():
        if token.length > 1:
          self._HandleError(
              errors.EXTRA_SPACE, 'Extra space after "%s"' %
              token.previous.string, token,
              position=Position(1, len(token.string) - 1))

    elif token_type == Type.OPERATOR:
      self._CheckOperator(token)
    elif token_type == Type.DOC_FLAG:
      flag = token.attached_object

      if flag.flag_type == 'bug':
        # TODO(robbyw): Check for exactly 1 space on the left.
        string = token.next.string.lstrip()
        string = string.split(' ', 1)[0]

        if not string.isdigit():
          self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
                            '@bug should be followed by a bug number', token)

      elif flag.flag_type == 'suppress':
        if flag.type is None:
          # A syntactically invalid suppress tag will get tokenized as a normal
          # flag, indicating an error.
          self._HandleError(
              errors.INCORRECT_SUPPRESS_SYNTAX,
              'Invalid suppress syntax: should be @suppress {errortype}. '
              'Spaces matter.', token)
        else:
          for suppress_type in flag.jstype.IterIdentifiers():
            if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES:
              self._HandleError(
                  errors.INVALID_SUPPRESS_TYPE,
                  'Invalid suppression type: %s' % suppress_type, token)

      elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and
            flag.flag_type == 'author'):
        # TODO(user): In non strict mode check the author tag for as much as
        # it exists, though the full form checked below isn't required.
        string = token.next.string
        result = self.AUTHOR_SPEC.match(string)
        if not result:
          self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION,
                            'Author tag line should be of the form: '
                            '@author [email protected] (Your Name)',
                            token.next)
        else:
          # Check spacing between email address and name. Do this before
          # checking earlier spacing so positions are easier to calculate for
          # autofixing.
          num_spaces = len(result.group(2))
          if num_spaces < 1:
            self._HandleError(errors.MISSING_SPACE,
                              'Missing space after email address',
                              token.next, position=Position(result.start(2), 0))
          elif num_spaces > 1:
            self._HandleError(
                errors.EXTRA_SPACE, 'Extra space after email address',
                token.next,
                position=Position(result.start(2) + 1, num_spaces - 1))

          # Check for extra spaces before email address. Can't be too few, if
          # not at least one we wouldn't match @author tag.
          num_spaces = len(result.group(1))
          if num_spaces > 1:
            self._HandleError(errors.EXTRA_SPACE,
                              'Extra space before email address',
                              token.next, position=Position(1, num_spaces - 1))

      elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and
            not self._limited_doc_checks):
        if flag.flag_type == 'param':
          if flag.name is None:
            self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
                              'Missing name in @param tag', token)

        if not flag.description or flag.description is None:
          flag_name = token.type
          if 'name' in token.values:
            flag_name = '@' + token.values['name']

          if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED:
            self._HandleError(
                errors.MISSING_JSDOC_TAG_DESCRIPTION,
                'Missing description in %s tag' % flag_name, token)
        else:
          self._CheckForMissingSpaceBeforeToken(flag.description_start_token)

      if flag.HasType():
        if flag.type_start_token is not None:
          self._CheckForMissingSpaceBeforeToken(
              token.attached_object.type_start_token)

        if flag.jstype and not flag.jstype.IsEmpty():
          self._CheckJsDocType(token, flag.jstype)

          if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and (
              flag.type_start_token.type != Type.DOC_START_BRACE or
              flag.type_end_token.type != Type.DOC_END_BRACE):
            self._HandleError(
                errors.MISSING_BRACES_AROUND_TYPE,
                'Type must always be surrounded by curly braces.', token)

    if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
      if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and
          token.values['name'] not in FLAGS.custom_jsdoc_tags):
        self._HandleError(
            errors.INVALID_JSDOC_TAG,
            'Invalid JsDoc tag: %s' % token.values['name'], token)

      if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and
          token.values['name'] == 'inheritDoc' and
          token_type == Type.DOC_INLINE_FLAG):
        self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
                          'Unnecessary braces around @inheritDoc',
                          token)

    elif token_type == Type.SIMPLE_LVALUE:
      identifier = token.values['identifier']

      if ((not state.InFunction() or state.InConstructor()) and
          state.InTopLevel() and not state.InObjectLiteralDescendant()):
        jsdoc = state.GetDocComment()
        if not state.HasDocComment(identifier):
          # Only test for documentation on identifiers with .s in them to
          # avoid checking things like simple variables. We don't require
          # documenting assignments to .prototype itself (bug 1880803).
          if (not state.InConstructor() and
              identifier.find('.') != -1 and not
              identifier.endswith('.prototype') and not
              self._limited_doc_checks):
            comment = state.GetLastComment()
            if not (comment and comment.lower().count('jsdoc inherited')):
              self._HandleError(
                  errors.MISSING_MEMBER_DOCUMENTATION,
                  "No docs found for member '%s'" % identifier,
                  token)
        elif jsdoc and (not state.InConstructor() or
                        identifier.startswith('this.')):
          # We are at the top level and the function/member is documented.
          if identifier.endswith('_') and not identifier.endswith('__'):
            # Can have a private class which inherits documentation from a
            # public superclass.
            #
            # @inheritDoc is deprecated in favor of using @override, and they
            if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor')
                and ('accessControls' not in jsdoc.suppressions)):
              self._HandleError(
                  errors.INVALID_OVERRIDE_PRIVATE,
                  '%s should not override a private member.' % identifier,
                  jsdoc.GetFlag('override').flag_token)
            if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor')
                and ('accessControls' not in jsdoc.suppressions)):
              self._HandleError(
                  errors.INVALID_INHERIT_DOC_PRIVATE,
                  '%s should not inherit from a private member.' % identifier,
                  jsdoc.GetFlag('inheritDoc').flag_token)
            if (not jsdoc.HasFlag('private') and
                ('underscore' not in jsdoc.suppressions) and not
                ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and
                 ('accessControls' in jsdoc.suppressions))):
              self._HandleError(
                  errors.MISSING_PRIVATE,
                  'Member "%s" must have @private JsDoc.' %
                  identifier, token)
            if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions:
              self._HandleError(
                  errors.UNNECESSARY_SUPPRESS,
                  '@suppress {underscore} is not necessary with @private',
                  jsdoc.suppressions['underscore'])
          elif (jsdoc.HasFlag('private') and
                not self.InExplicitlyTypedLanguage()):
            # It is convention to hide public fields in some ECMA
            # implementations from documentation using the @private tag.
            self._HandleError(
                errors.EXTRA_PRIVATE,
                'Member "%s" must not have @private JsDoc' %
                identifier, token)

          # These flags are only legal on localizable message definitions;
          # such variables always begin with the prefix MSG_.
          if not identifier.startswith('MSG_') and '.MSG_' not in identifier:
            for f in ('desc', 'hidden', 'meaning'):
              if jsdoc.HasFlag(f):
                self._HandleError(
                    errors.INVALID_USE_OF_DESC_TAG,
                    'Member "%s" does not start with MSG_ and thus '
                    'should not have @%s JsDoc' % (identifier, f),
                    token)

      # Check for illegaly assigning live objects as prototype property values.
      index = identifier.find('.prototype.')
      # Ignore anything with additional .s after the prototype.
      if index != -1 and identifier.find('.', index + 11) == -1:
        equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
        next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES)
        if next_code and (
            next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or
            next_code.IsOperator('new')):
          self._HandleError(
              errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
              'Member %s cannot have a non-primitive value' % identifier,
              token)

    elif token_type == Type.END_PARAMETERS:
      # Find extra space at the end of parameter lists.  We check the token
      # prior to the current one when it is a closing paren.
      if (token.previous and token.previous.type == Type.PARAMETERS
          and self.ENDS_WITH_SPACE.search(token.previous.string)):
        self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
                          token.previous)

      jsdoc = state.GetDocComment()
      if state.GetFunction().is_interface:
        if token.previous and token.previous.type == Type.PARAMETERS:
          self._HandleError(
              errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
              'Interface constructor cannot have parameters',
              token.previous)
      elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
            and not jsdoc.InheritsDocumentation()
            and not state.InObjectLiteralDescendant() and not
            jsdoc.IsInvalidated()):
        distance, edit = jsdoc.CompareParameters(state.GetParams())
        if distance:
          params_iter = iter(state.GetParams())
          docs_iter = iter(jsdoc.ordered_params)

          for op in edit:
            if op == 'I':
              # Insertion.
              # Parsing doc comments is the same for all languages
              # but some languages care about parameters that don't have
              # doc comments and some languages don't care.
              # Languages that don't allow variables to by typed such as
              # JavaScript care but languages such as ActionScript or Java
              # that allow variables to be typed don't care.
              if not self._limited_doc_checks:
                self.HandleMissingParameterDoc(token, params_iter.next())

            elif op == 'D':
              # Deletion
              self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION,
                                'Found docs for non-existing parameter: "%s"' %
                                docs_iter.next(), token)
            elif op == 'S':
              # Substitution
              if not self._limited_doc_checks:
                self._HandleError(
                    errors.WRONG_PARAMETER_DOCUMENTATION,
                    'Parameter mismatch: got "%s", expected "%s"' %
                    (params_iter.next(), docs_iter.next()), token)

            else:
              # Equality - just advance the iterators
              params_iter.next()
              docs_iter.next()

    elif token_type == Type.STRING_TEXT:
      # If this is the first token after the start of the string, but it's at
      # the end of a line, we know we have a multi-line string.
      if token.previous.type in (
          Type.SINGLE_QUOTE_STRING_START,
          Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
        self._HandleError(errors.MULTI_LINE_STRING,
                          'Multi-line strings are not allowed', token)

    # This check is orthogonal to the ones above, and repeats some types, so
    # it is a plain if and not an elif.
    if token.type in Type.COMMENT_TYPES:
      if self.ILLEGAL_TAB.search(token.string):
        self._HandleError(errors.ILLEGAL_TAB,
                          'Illegal tab in comment "%s"' % token.string, token)

      trimmed = token.string.rstrip()
      if last_in_line and token.string != trimmed:
        # Check for extra whitespace at the end of a line.
        self._HandleError(
            errors.EXTRA_SPACE, 'Extra space at end of line', token,
            position=Position(len(trimmed), len(token.string) - len(trimmed)))

    # This check is also orthogonal since it is based on metadata.
    if token.metadata.is_implied_semicolon:
      self._HandleError(errors.MISSING_SEMICOLON,
                        'Missing semicolon at end of line', token)
Exemple #5
0
    def CheckToken(self, token, state):
        """Checks a token for indentation errors.

    Args:
      token: The current token under consideration
      state: Additional information about the current tree state

    Returns:
      An error array [error code, error string, error token] if the token is
      improperly indented, or None if indentation is correct.
    """

        token_type = token.type
        indentation_errors = []
        stack = self._stack
        is_first = self._IsFirstNonWhitespaceTokenInLine(token)

        # Add tokens that could decrease indentation before checking.
        if token_type == Type.END_PAREN:
            self._PopTo(Type.START_PAREN)

        elif token_type == Type.END_PARAMETERS:
            self._PopTo(Type.START_PARAMETERS)

        elif token_type == Type.END_BRACKET:
            self._PopTo(Type.START_BRACKET)

        elif token_type == Type.END_BLOCK:
            start_token = self._PopTo(Type.START_BLOCK)
            # Check for required goog.scope comment.
            if start_token:
                goog_scope = tokenutil.GoogScopeOrNoneFromStartBlock(
                    start_token.token)
                if goog_scope is not None:
                    if not token.line.endswith(';  // goog.scope\n'):
                        if (token.line.find('//') > -1
                                and token.line.find('goog.scope') >
                                token.line.find('//')):
                            indentation_errors.append([
                                errors.MALFORMED_END_OF_SCOPE_COMMENT,
                                ('Malformed end of goog.scope comment. Please use the '
                                 'exact following syntax to close the scope:\n'
                                 '});  // goog.scope'), token,
                                Position(token.start_index, token.length)
                            ])
                        else:
                            indentation_errors.append([
                                errors.MISSING_END_OF_SCOPE_COMMENT,
                                ('Missing comment for end of goog.scope which opened at line '
                                 '%d. End the scope with:\n'
                                 '});  // goog.scope' %
                                 (start_token.line_number)), token,
                                Position(token.start_index, token.length)
                            ])

        elif token_type == Type.KEYWORD and token.string in ('case',
                                                             'default'):
            self._Add(self._PopTo(Type.START_BLOCK))

        elif is_first and token.string == '.':
            # This token should have been on the previous line, so treat it as if it
            # was there.
            info = TokenInfo(token)
            info.line_number = token.line_number - 1
            self._Add(info)

        elif token_type == Type.SEMICOLON:
            self._PopTransient()

        not_binary_operator = (token_type != Type.OPERATOR
                               or token.metadata.IsUnaryOperator())
        not_dot = token.string != '.'
        if is_first and not_binary_operator and not_dot and token.type not in (
                Type.COMMENT, Type.DOC_PREFIX, Type.STRING_TEXT):
            if flags.FLAGS.debug_indentation:
                print 'Line #%d: stack %r' % (token.line_number, stack)

            # Ignore lines that start in JsDoc since we don't check them properly yet.
            # TODO(robbyw): Support checking JsDoc indentation.
            # Ignore lines that start as multi-line strings since indentation is N/A.
            # Ignore lines that start with operators since we report that already.
            # Ignore lines with tabs since we report that already.
            expected = self._GetAllowableIndentations()
            actual = self._GetActualIndentation(token)

            # Special case comments describing else, case, and default.  Allow them
            # to outdent to the parent block.
            if token_type in Type.COMMENT_TYPES:
                next_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
                if next_code and next_code.type == Type.END_BLOCK:
                    next_code = tokenutil.SearchExcept(next_code,
                                                       Type.NON_CODE_TYPES)
                if next_code and next_code.string in ('else', 'case',
                                                      'default'):
                    # TODO(robbyw): This almost certainly introduces false negatives.
                    expected |= self._AddToEach(expected, -2)

            if actual >= 0 and actual not in expected:
                expected = sorted(expected)
                indentation_errors.append([
                    errors.WRONG_INDENTATION,
                    'Wrong indentation: expected any of {%s} but got %d' %
                    (', '.join(['%d' % x for x in expected]), actual), token,
                    Position(actual, expected[0])
                ])
                self._start_index_offset[
                    token.line_number] = expected[0] - actual

        # Add tokens that could increase indentation.
        if token_type == Type.START_BRACKET:
            self._Add(
                TokenInfo(token=token,
                          is_block=token.metadata.context.type ==
                          Context.ARRAY_LITERAL))

        elif token_type == Type.START_BLOCK or token.metadata.is_implied_block:
            self._Add(TokenInfo(token=token, is_block=True))

        elif token_type in (Type.START_PAREN, Type.START_PARAMETERS):
            self._Add(TokenInfo(token=token, is_block=False))

        elif token_type == Type.KEYWORD and token.string == 'return':
            self._Add(TokenInfo(token))

        elif not token.IsLastInLine() and (token.IsAssignment()
                                           or token.IsOperator('?')):
            self._Add(TokenInfo(token=token))

        # Handle implied block closes.
        if token.metadata.is_implied_block_close:
            self._PopToImpliedBlock()

        # Add some tokens only if they appear at the end of the line.
        is_last = self._IsLastCodeInLine(token)
        if is_last:
            if token_type == Type.OPERATOR:
                if token.string == ':':
                    if stack and stack[-1].token.string == '?':
                        # When a ternary : is on a different line than its '?', it doesn't
                        # add indentation.
                        if token.line_number == stack[-1].token.line_number:
                            self._Add(TokenInfo(token))
                    elif token.metadata.context.type == Context.CASE_BLOCK:
                        # Pop transient tokens from say, line continuations, e.g.,
                        # case x.
                        #     y:
                        # Want to pop the transient 4 space continuation indent.
                        self._PopTransient()
                        # Starting the body of the case statement, which is a type of
                        # block.
                        self._Add(TokenInfo(token=token, is_block=True))
                    elif token.metadata.context.type == Context.LITERAL_ELEMENT:
                        # When in an object literal, acts as operator indicating line
                        # continuations.
                        self._Add(TokenInfo(token))
                        pass
                    else:
                        # ':' might also be a statement label, no effect on indentation in
                        # this case.
                        pass

                elif token.string != ',':
                    self._Add(TokenInfo(token))
                else:
                    # The token is a comma.
                    if token.metadata.context.type == Context.VAR:
                        self._Add(TokenInfo(token))
                    elif token.metadata.context.type != Context.PARAMETERS:
                        self._PopTransient()

            elif (token.string.endswith('.')
                  and token_type in (Type.IDENTIFIER, Type.NORMAL)):
                self._Add(TokenInfo(token))
            elif token_type == Type.PARAMETERS and token.string.endswith(','):
                # Parameter lists.
                self._Add(TokenInfo(token))
            elif token.IsKeyword('var'):
                self._Add(TokenInfo(token))
            elif token.metadata.is_implied_semicolon:
                self._PopTransient()
        elif token.IsAssignment():
            self._Add(TokenInfo(token))

        return indentation_errors
Exemple #6
0
    def CheckToken(self, token, state):
        """Checks a token, given the current parser_state, for warnings and errors.

    Args:
      token: The current token under consideration
      state: parser_state object that indicates the current state in the page
    """
        # Store some convenience variables
        first_in_line = token.IsFirstInLine()
        last_in_line = token.IsLastInLine()
        last_non_space_token = state.GetLastNonSpaceToken()

        type = token.type

        # Process the line change.
        if not self._is_html and FLAGS.strict:
            # TODO(robbyw): Support checking indentation in HTML files.
            indentation_errors = self._indentation.CheckToken(token, state)
            for indentation_error in indentation_errors:
                self._HandleError(*indentation_error)

        if last_in_line:
            self._CheckLineLength(token, state)

        if type == Type.PARAMETERS:
            # Find missing spaces in parameter lists.
            if self.MISSING_PARAMETER_SPACE.search(token.string):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space after ","', token)

            # Find extra spaces at the beginning of parameter lists.  Make sure
            # we aren't at the beginning of a continuing multi-line list.
            if not first_in_line:
                space_count = len(token.string) - len(token.string.lstrip())
                if space_count:
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space after "("', token,
                                      Position(0, space_count))

        elif (type == Type.START_BLOCK
              and token.metadata.context.type == Context.BLOCK):
            self._CheckForMissingSpaceBeforeToken(token)

        elif type == Type.END_BLOCK:
            # This check is for object literal end block tokens, but there is no need
            # to test that condition since a comma at the end of any other kind of
            # block is undoubtedly a parse error.
            last_code = token.metadata.last_code
            if last_code.IsOperator(','):
                self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
                                  'Illegal comma at end of object literal',
                                  last_code, Position.All(last_code.string))

            if state.InFunction() and state.IsFunctionClose():
                is_immediately_called = (token.next and token.next.type
                                         == Type.START_PAREN)
                if state.InTopLevelFunction():
                    # When the function was top-level and not immediately called, check
                    # that it's terminated by a semi-colon.
                    if state.InAssignedFunction():
                        if not is_immediately_called and (
                                last_in_line
                                or not token.next.type == Type.SEMICOLON):
                            self._HandleError(
                                errors.MISSING_SEMICOLON_AFTER_FUNCTION,
                                'Missing semicolon after function assigned to a variable',
                                token, Position.AtEnd(token.string))
                    else:
                        if not last_in_line and token.next.type == Type.SEMICOLON:
                            self._HandleError(
                                errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION,
                                'Illegal semicolon after function declaration',
                                token.next, Position.All(token.next.string))

                if (state.InInterfaceMethod()
                        and last_code.type != Type.START_BLOCK):
                    self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE,
                                      'Interface methods cannot contain code',
                                      last_code)

            elif (state.IsBlockClose() and token.next
                  and token.next.type == Type.SEMICOLON):
                self._HandleError(
                    errors.REDUNDANT_SEMICOLON,
                    'No semicolon is required to end a code block', token.next,
                    Position.All(token.next.string))

        elif type == Type.SEMICOLON:
            if token.previous and token.previous.type == Type.WHITESPACE:
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"',
                                  token.previous,
                                  Position.All(token.previous.string))

            if token.next and token.next.line_number == token.line_number:
                if token.metadata.context.type != Context.FOR_GROUP_BLOCK:
                    # TODO(robbyw): Error about no multi-statement lines.
                    pass

                elif token.next.type not in (Type.WHITESPACE, Type.SEMICOLON,
                                             Type.END_PAREN):
                    self._HandleError(
                        errors.MISSING_SPACE,
                        'Missing space after ";" in for statement', token.next,
                        Position.AtBeginning())

            last_code = token.metadata.last_code
            if last_code and last_code.type == Type.SEMICOLON:
                # Allow a single double semi colon in for loops for cases like:
                # for (;;) { }.
                # NOTE(user): This is not a perfect check, and will not throw an error
                # for cases like: for (var i = 0;; i < n; i++) {}, but then your code
                # probably won't work either.
                for_token = tokenutil.CustomSearch(
                    last_code,
                    lambda token: token.type == Type.KEYWORD and token.string
                    == 'for',
                    end_func=lambda token: token.type == Type.SEMICOLON,
                    distance=None,
                    reverse=True)

                if not for_token:
                    self._HandleError(errors.REDUNDANT_SEMICOLON,
                                      'Redundant semicolon', token,
                                      Position.All(token.string))

        elif type == Type.START_PAREN:
            if token.previous and token.previous.type == Type.KEYWORD:
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "("', token,
                                  Position.AtBeginning())
            elif token.previous and token.previous.type == Type.WHITESPACE:
                before_space = token.previous.previous
                if (before_space
                        and before_space.line_number == token.line_number
                        and before_space.type == Type.IDENTIFIER):
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space before "("', token.previous,
                                      Position.All(token.previous.string))

        elif type == Type.START_BRACKET:
            if (not first_in_line and token.previous.type == Type.WHITESPACE
                    and last_non_space_token and last_non_space_token.type
                    in Type.EXPRESSION_ENDER_TYPES):
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["',
                                  token.previous,
                                  Position.All(token.previous.string))
            # If the [ token is the first token in a line we shouldn't complain
            # about a missing space before [.  This is because some Ecma script
            # languages allow syntax like:
            # [Annotation]
            # class MyClass {...}
            # So we don't want to blindly warn about missing spaces before [.
            # In the the future, when rules for computing exactly how many spaces
            # lines should be indented are added, then we can return errors for
            # [ tokens that are improperly indented.
            # For example:
            # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName =
            # [a,b,c];
            # should trigger a proper indentation warning message as [ is not indented
            # by four spaces.
            elif (not first_in_line and token.previous
                  and not token.previous.type
                  in ([Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] +
                      Type.EXPRESSION_ENDER_TYPES)):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "["', token,
                                  Position.AtBeginning())

        elif type in (Type.END_PAREN, Type.END_BRACKET):
            # Ensure there is no space before closing parentheses, except when
            # it's in a for statement with an omitted section, or when it's at the
            # beginning of a line.
            if (token.previous and token.previous.type == Type.WHITESPACE
                    and not token.previous.IsFirstInLine() and
                    not (last_non_space_token and
                         last_non_space_token.line_number == token.line_number
                         and last_non_space_token.type == Type.SEMICOLON)):
                self._HandleError(errors.EXTRA_SPACE,
                                  'Extra space before "%s"' % token.string,
                                  token.previous,
                                  Position.All(token.previous.string))

            if token.type == Type.END_BRACKET:
                last_code = token.metadata.last_code
                if last_code.IsOperator(','):
                    self._HandleError(errors.COMMA_AT_END_OF_LITERAL,
                                      'Illegal comma at end of array literal',
                                      last_code,
                                      Position.All(last_code.string))

        elif type == Type.WHITESPACE:
            if self.ILLEGAL_TAB.search(token.string):
                if token.IsFirstInLine():
                    self._HandleError(
                        errors.ILLEGAL_TAB,
                        'Illegal tab in whitespace before "%s"' %
                        token.next.string, token, Position.All(token.string))
                else:
                    self._HandleError(
                        errors.ILLEGAL_TAB,
                        'Illegal tab in whitespace after "%s"' %
                        token.previous.string, token,
                        Position.All(token.string))

            # Check whitespace length if it's not the first token of the line and
            # if it's not immediately before a comment.
            if last_in_line:
                # Check for extra whitespace at the end of a line.
                self._HandleError(errors.EXTRA_SPACE,
                                  'Extra space at end of line', token,
                                  Position.All(token.string))
            elif not first_in_line and not token.next.IsComment():
                if token.length > 1:
                    self._HandleError(
                        errors.EXTRA_SPACE,
                        'Extra space after "%s"' % token.previous.string,
                        token, Position(1,
                                        len(token.string) - 1))

        elif type == Type.OPERATOR:
            last_code = token.metadata.last_code

            if not self._ExpectSpaceBeforeOperator(token):
                if (token.previous and token.previous.type == Type.WHITESPACE
                        and last_code
                        and last_code.type in (Type.NORMAL, Type.IDENTIFIER)):
                    self._HandleError(errors.EXTRA_SPACE,
                                      'Extra space before "%s"' % token.string,
                                      token.previous,
                                      Position.All(token.previous.string))

            elif (token.previous and not token.previous.IsComment()
                  and token.previous.type in Type.EXPRESSION_ENDER_TYPES):
                self._HandleError(errors.MISSING_SPACE,
                                  'Missing space before "%s"' % token.string,
                                  token, Position.AtBeginning())

            # Check that binary operators are not used to start lines.
            if ((not last_code or last_code.line_number != token.line_number)
                    and not token.metadata.IsUnaryOperator()):
                self._HandleError(
                    errors.LINE_STARTS_WITH_OPERATOR,
                    'Binary operator should go on previous line "%s"' %
                    token.string, token)

        elif type == Type.DOC_FLAG:
            flag = token.attached_object

            if flag.flag_type == 'bug':
                # TODO(robbyw): Check for exactly 1 space on the left.
                string = token.next.string.lstrip()
                string = string.split(' ', 1)[0]

                if not string.isdigit():
                    self._HandleError(
                        errors.NO_BUG_NUMBER_AFTER_BUG_TAG,
                        '@bug should be followed by a bug number', token)

            elif flag.flag_type == 'suppress':
                if flag.type is None:
                    # A syntactically invalid suppress tag will get tokenized as a normal
                    # flag, indicating an error.
                    self._HandleError(
                        errors.INCORRECT_SUPPRESS_SYNTAX,
                        'Invalid suppress syntax: should be @suppress {errortype}. '
                        'Spaces matter.', token)
                elif flag.type not in state.GetDocFlag().SUPPRESS_TYPES:
                    self._HandleError(
                        errors.INVALID_SUPPRESS_TYPE,
                        'Invalid suppression type: %s' % flag.type, token)

            elif FLAGS.strict and flag.flag_type == 'author':
                # TODO(user): In non strict mode check the author tag for as much as
                # it exists, though the full form checked below isn't required.
                string = token.next.string
                result = self.AUTHOR_SPEC.match(string)
                if not result:
                    self._HandleError(
                        errors.INVALID_AUTHOR_TAG_DESCRIPTION,
                        'Author tag line should be of the form: '
                        '@author [email protected] (Your Name)', token.next)
                else:
                    # Check spacing between email address and name. Do this before
                    # checking earlier spacing so positions are easier to calculate for
                    # autofixing.
                    num_spaces = len(result.group(2))
                    if num_spaces < 1:
                        self._HandleError(errors.MISSING_SPACE,
                                          'Missing space after email address',
                                          token.next,
                                          Position(result.start(2), 0))
                    elif num_spaces > 1:
                        self._HandleError(
                            errors.EXTRA_SPACE,
                            'Extra space after email address', token.next,
                            Position(result.start(2) + 1, num_spaces - 1))

                    # Check for extra spaces before email address. Can't be too few, if
                    # not at least one we wouldn't match @author tag.
                    num_spaces = len(result.group(1))
                    if num_spaces > 1:
                        self._HandleError(errors.EXTRA_SPACE,
                                          'Extra space before email address',
                                          token.next,
                                          Position(1, num_spaces - 1))

            elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION
                  and not self._limited_doc_checks):
                if flag.flag_type == 'param':
                    if flag.name is None:
                        self._HandleError(errors.MISSING_JSDOC_PARAM_NAME,
                                          'Missing name in @param tag', token)

                if not flag.description or flag.description is None:
                    flag_name = token.type
                    if 'name' in token.values:
                        flag_name = '@' + token.values['name']
                    self._HandleError(
                        errors.MISSING_JSDOC_TAG_DESCRIPTION,
                        'Missing description in %s tag' % flag_name, token)
                else:
                    self._CheckForMissingSpaceBeforeToken(
                        flag.description_start_token)

                    # We want punctuation to be inside of any tags ending a description,
                    # so strip tags before checking description. See bug 1127192. Note
                    # that depending on how lines break, the real description end token
                    # may consist only of stripped html and the effective end token can
                    # be different.
                    end_token = flag.description_end_token
                    end_string = htmlutil.StripTags(end_token.string).strip()
                    while (end_string == ''
                           and not end_token.type in Type.FLAG_ENDING_TYPES):
                        end_token = end_token.previous
                        if end_token.type in Type.FLAG_DESCRIPTION_TYPES:
                            end_string = htmlutil.StripTags(
                                end_token.string).rstrip()

                    if not (end_string.endswith('.')
                            or end_string.endswith('?')
                            or end_string.endswith('!')):
                        # Find the position for the missing punctuation, inside of any html
                        # tags.
                        desc_str = end_token.string.rstrip()
                        while desc_str.endswith('>'):
                            start_tag_index = desc_str.rfind('<')
                            if start_tag_index < 0:
                                break
                            desc_str = desc_str[:start_tag_index].rstrip()
                        end_position = Position(len(desc_str), 0)

                        self._HandleError(
                            errors.
                            JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER,
                            ('%s descriptions must end with valid punctuation such as a '
                             'period.' % token.string), end_token,
                            end_position)

            if flag.flag_type in state.GetDocFlag().HAS_TYPE:
                if flag.type_start_token is not None:
                    self._CheckForMissingSpaceBeforeToken(
                        token.attached_object.type_start_token)

                if flag.type and flag.type != '' and not flag.type.isspace():
                    self._CheckJsDocType(token)

        if type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
            if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC
                    and token.values['name'] not in FLAGS.custom_jsdoc_tags):
                self._HandleError(
                    errors.INVALID_JSDOC_TAG,
                    'Invalid JsDoc tag: %s' % token.values['name'], token)

            if (FLAGS.strict and token.values['name'] == 'inheritDoc'
                    and type == Type.DOC_INLINE_FLAG):
                self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC,
                                  'Unnecessary braces around @inheritDoc',
                                  token)

        elif type == Type.SIMPLE_LVALUE:
            identifier = token.values['identifier']

            if ((not state.InFunction() or state.InConstructor())
                    and not state.InParentheses()
                    and not state.InObjectLiteralDescendant()):
                jsdoc = state.GetDocComment()
                if not state.HasDocComment(identifier):
                    # Only test for documentation on identifiers with .s in them to
                    # avoid checking things like simple variables. We don't require
                    # documenting assignments to .prototype itself (bug 1880803).
                    if (not state.InConstructor()
                            and identifier.find('.') != -1
                            and not identifier.endswith('.prototype')
                            and not self._limited_doc_checks):
                        comment = state.GetLastComment()
                        if not (comment
                                and comment.lower().count('jsdoc inherited')):
                            self._HandleError(
                                errors.MISSING_MEMBER_DOCUMENTATION,
                                "No docs found for member '%s'" % identifier,
                                token)
                elif jsdoc and (not state.InConstructor()
                                or identifier.startswith('this.')):
                    # We are at the top level and the function/member is documented.
                    if identifier.endswith(
                            '_') and not identifier.endswith('__'):
                        if jsdoc.HasFlag('override'):
                            self._HandleError(
                                errors.INVALID_OVERRIDE_PRIVATE,
                                '%s should not override a private member.' %
                                identifier,
                                jsdoc.GetFlag('override').flag_token)
                        # Can have a private class which inherits documentation from a
                        # public superclass.
                        if jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag(
                                'constructor'):
                            self._HandleError(
                                errors.INVALID_INHERIT_DOC_PRIVATE,
                                '%s should not inherit from a private member.'
                                % identifier,
                                jsdoc.GetFlag('inheritDoc').flag_token)
                        if (not jsdoc.HasFlag('private')
                                and not ('underscore' in jsdoc.suppressions)):
                            self._HandleError(
                                errors.MISSING_PRIVATE,
                                'Member "%s" must have @private JsDoc.' %
                                identifier, token)
                        if jsdoc.HasFlag(
                                'private'
                        ) and 'underscore' in jsdoc.suppressions:
                            self._HandleError(
                                errors.UNNECESSARY_SUPPRESS,
                                '@suppress {underscore} is not necessary with @private',
                                jsdoc.suppressions['underscore'])
                    elif jsdoc.HasFlag('private'):
                        self._HandleError(
                            errors.EXTRA_PRIVATE,
                            'Member "%s" must not have @private JsDoc' %
                            identifier, token)

                    if ((jsdoc.HasFlag('desc') or jsdoc.HasFlag('hidden'))
                            and not identifier.startswith('MSG_')
                            and identifier.find('.MSG_') == -1):
                        # TODO(user): Update error message to show the actual invalid
                        # tag, either @desc or @hidden.
                        self._HandleError(
                            errors.INVALID_USE_OF_DESC_TAG,
                            'Member "%s" should not have @desc JsDoc' %
                            identifier, token)

            # Check for illegaly assigning live objects as prototype property values.
            index = identifier.find('.prototype.')
            # Ignore anything with additional .s after the prototype.
            if index != -1 and identifier.find('.', index + 11) == -1:
                equal_operator = tokenutil.SearchExcept(
                    token, Type.NON_CODE_TYPES)
                next_code = tokenutil.SearchExcept(equal_operator,
                                                   Type.NON_CODE_TYPES)
                if next_code and (next_code.type
                                  in (Type.START_BRACKET, Type.START_BLOCK)
                                  or next_code.IsOperator('new')):
                    self._HandleError(
                        errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE,
                        'Member %s cannot have a non-primitive value' %
                        identifier, token)

        elif type == Type.END_PARAMETERS:
            # Find extra space at the end of parameter lists.  We check the token
            # prior to the current one when it is a closing paren.
            if (token.previous and token.previous.type == Type.PARAMETERS
                    and self.ENDS_WITH_SPACE.search(token.previous.string)):
                self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"',
                                  token.previous)

            jsdoc = state.GetDocComment()
            if state.GetFunction().is_interface:
                if token.previous and token.previous.type == Type.PARAMETERS:
                    self._HandleError(
                        errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS,
                        'Interface constructor cannot have parameters',
                        token.previous)
            elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see')
                  and not jsdoc.InheritsDocumentation()
                  and not state.InObjectLiteralDescendant()
                  and not jsdoc.IsInvalidated()):
                distance, edit = jsdoc.CompareParameters(state.GetParams())
                if distance:
                    params_iter = iter(state.GetParams())
                    docs_iter = iter(jsdoc.ordered_params)

                    for op in edit:
                        if op == 'I':
                            # Insertion.
                            # Parsing doc comments is the same for all languages
                            # but some languages care about parameters that don't have
                            # doc comments and some languages don't care.
                            # Languages that don't allow variables to by typed such as
                            # JavaScript care but languages such as ActionScript or Java
                            # that allow variables to be typed don't care.
                            if not self._limited_doc_checks:
                                self.HandleMissingParameterDoc(
                                    token, params_iter.next())

                        elif op == 'D':
                            # Deletion
                            self._HandleError(
                                errors.EXTRA_PARAMETER_DOCUMENTATION,
                                'Found docs for non-existing parameter: "%s"' %
                                docs_iter.next(), token)
                        elif op == 'S':
                            # Substitution
                            if not self._limited_doc_checks:
                                self._HandleError(
                                    errors.WRONG_PARAMETER_DOCUMENTATION,
                                    'Parameter mismatch: got "%s", expected "%s"'
                                    % (params_iter.next(), docs_iter.next()),
                                    token)

                        else:
                            # Equality - just advance the iterators
                            params_iter.next()
                            docs_iter.next()

        elif type == Type.STRING_TEXT:
            # If this is the first token after the start of the string, but it's at
            # the end of a line, we know we have a multi-line string.
            if token.previous.type in (
                    Type.SINGLE_QUOTE_STRING_START,
                    Type.DOUBLE_QUOTE_STRING_START) and last_in_line:
                self._HandleError(errors.MULTI_LINE_STRING,
                                  'Multi-line strings are not allowed', token)

        # This check is orthogonal to the ones above, and repeats some types, so
        # it is a plain if and not an elif.
        if token.type in Type.COMMENT_TYPES:
            if self.ILLEGAL_TAB.search(token.string):
                self._HandleError(errors.ILLEGAL_TAB,
                                  'Illegal tab in comment "%s"' % token.string,
                                  token)

            trimmed = token.string.rstrip()
            if last_in_line and token.string != trimmed:
                # Check for extra whitespace at the end of a line.
                self._HandleError(
                    errors.EXTRA_SPACE, 'Extra space at end of line', token,
                    Position(len(trimmed),
                             len(token.string) - len(trimmed)))

        # This check is also orthogonal since it is based on metadata.
        if token.metadata.is_implied_semicolon:
            self._HandleError(errors.MISSING_SEMICOLON,
                              'Missing semicolon at end of line', token)
Exemple #7
0
    def HandleToken(self, token, last_non_space_token):
        """Handles the given token and updates state.

    Args:
      token: The token to handle.
      last_non_space_token:
    """
        self._is_block_close = False

        if not self._first_token:
            self._first_token = token

        # Track block depth.
        type = token.type
        if type == Type.START_BLOCK:
            self._block_depth += 1

            # Subclasses need to handle block start very differently because
            # whether a block is a CODE or OBJECT_LITERAL block varies significantly
            # by language.
            self._block_types.append(self.GetBlockType(token))

            # When entering a function body, record its parameters.
            if self.InFunction():
                function = self._function_stack[-1]
                if self._block_depth == function.block_depth + 1:
                    function.parameters = self.GetParams()

        # Track block depth.
        elif type == Type.END_BLOCK:
            self._is_block_close = not self.InObjectLiteral()
            self._block_depth -= 1
            self._block_types.pop()

        # Track parentheses depth.
        elif type == Type.START_PAREN:
            self._paren_depth += 1

        # Track parentheses depth.
        elif type == Type.END_PAREN:
            self._paren_depth -= 1

        elif type == Type.COMMENT:
            self._last_comment = token.string

        elif type == Type.START_DOC_COMMENT:
            self._last_comment = None
            self._doc_comment = DocComment(token)

        elif type == Type.END_DOC_COMMENT:
            self._doc_comment.end_token = token

        elif type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG):
            flag = self._doc_flag(token)
            token.attached_object = flag
            self._doc_comment.AddFlag(flag)

            if flag.flag_type == 'suppress':
                self._doc_comment.AddSuppression(token)

        elif type == Type.FUNCTION_DECLARATION:
            last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
                                               None, True)
            doc = None
            # Only functions outside of parens are eligible for documentation.
            if not self._paren_depth:
                doc = self._doc_comment

            name = ''
            is_assigned = last_code and (
                last_code.IsOperator('=') or last_code.IsOperator('||')
                or last_code.IsOperator('&&') or
                (last_code.IsOperator(':') and not self.InObjectLiteral()))
            if is_assigned:
                # TODO(robbyw): This breaks for x[2] = ...
                # Must use loop to find full function name in the case of line-wrapped
                # declarations (bug 1220601) like:
                # my.function.foo.
                #   bar = function() ...
                identifier = tokenutil.Search(last_code, Type.SIMPLE_LVALUE,
                                              None, True)
                while identifier and identifier.type in (Type.IDENTIFIER,
                                                         Type.SIMPLE_LVALUE):
                    name = identifier.string + name
                    # Traverse behind us, skipping whitespace and comments.
                    while True:
                        identifier = identifier.previous
                        if not identifier or not identifier.type in Type.NON_CODE_TYPES:
                            break

            else:
                next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
                while next_token and next_token.IsType(Type.FUNCTION_NAME):
                    name += next_token.string
                    next_token = tokenutil.Search(next_token,
                                                  Type.FUNCTION_NAME, 2)

            function = Function(self._block_depth, is_assigned, doc, name)
            function.start_token = token

            self._function_stack.append(function)
            self._functions_by_name[name] = function

            # Add a delimiter in stack for scope variables to define start of
            # function. This helps in popping variables of this function when
            # function declaration ends.
            self._variables_in_scope.append('')

        elif type == Type.START_PARAMETERS:
            self._cumulative_params = ''

        elif type == Type.PARAMETERS:
            self._cumulative_params += token.string
            self._variables_in_scope.extend(self.GetParams())

        elif type == Type.KEYWORD and token.string == 'return':
            next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
            if not next_token.IsType(Type.SEMICOLON):
                function = self.GetFunction()
                if function:
                    function.has_return = True

        elif type == Type.KEYWORD and token.string == 'throw':
            function = self.GetFunction()
            if function:
                function.has_throw = True

        elif type == Type.KEYWORD and token.string == 'var':
            function = self.GetFunction()
            next_token = tokenutil.Search(
                token, [Type.IDENTIFIER, Type.SIMPLE_LVALUE])

            if next_token:
                if next_token.type == Type.SIMPLE_LVALUE:
                    self._variables_in_scope.append(
                        next_token.values['identifier'])
                else:
                    self._variables_in_scope.append(next_token.string)

        elif type == Type.SIMPLE_LVALUE:
            identifier = token.values['identifier']
            jsdoc = self.GetDocComment()
            if jsdoc:
                self._documented_identifiers.add(identifier)

            self._HandleIdentifier(identifier, True)

        elif type == Type.IDENTIFIER:
            self._HandleIdentifier(token.string, False)

            # Detect documented non-assignments.
            next_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES)
            if next_token and next_token.IsType(Type.SEMICOLON):
                if (self._last_non_space_token
                        and self._last_non_space_token.IsType(
                            Type.END_DOC_COMMENT)):
                    self._documented_identifiers.add(token.string)
  def _ProcessContext(self):
    """Process the context at the current token.

    Returns:
      The context that should be assigned to the current token, or None if
      the current context after this method should be used.

    Raises:
      ParseError: When the token appears in an invalid context.
    """
    token = self._token
    token_type = token.type

    if self._context.type in EcmaContext.BLOCK_TYPES:
      # Whenever we're in a block, we add a statement context.  We make an
      # exception for switch statements since they can only contain case: and
      # default: and therefore don't directly contain statements.
      # The block we add here may be immediately removed in some cases, but
      # that causes no harm.
      parent = self._context.parent
      if not parent or parent.type != EcmaContext.SWITCH:
        self._AddContext(EcmaContext.STATEMENT)

    elif self._context.type == EcmaContext.ARRAY_LITERAL:
      self._AddContext(EcmaContext.LITERAL_ELEMENT)

    if token_type == TokenType.START_PAREN:
      if self._last_code and self._last_code.IsKeyword('for'):
        # for loops contain multiple statements in the group unlike while,
        # switch, if, etc.
        self._AddContext(EcmaContext.FOR_GROUP_BLOCK)
      else:
        self._AddContext(EcmaContext.GROUP)

    elif token_type == TokenType.END_PAREN:
      result = self._PopContextType(EcmaContext.GROUP,
                                    EcmaContext.FOR_GROUP_BLOCK)
      keyword_token = result.start_token.metadata.last_code
      # keyword_token will not exist if the open paren is the first line of the
      # file, for example if all code is wrapped in an immediately executed
      # annonymous function.
      if keyword_token and keyword_token.string in ('if', 'for', 'while'):
        next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
        if next_code.type != TokenType.START_BLOCK:
          # Check for do-while.
          is_do_while = False
          pre_keyword_token = keyword_token.metadata.last_code
          if (pre_keyword_token and
              pre_keyword_token.type == TokenType.END_BLOCK):
            start_block_token = pre_keyword_token.metadata.context.start_token
            is_do_while = start_block_token.metadata.last_code.string == 'do'

          # If it's not do-while, it's an implied block.
          if not is_do_while:
            self._AddContext(EcmaContext.IMPLIED_BLOCK)
            token.metadata.is_implied_block = True

      return result

    # else (not else if) with no open brace after it should be considered the
    # start of an implied block, similar to the case with if, for, and while
    # above.
    elif (token_type == TokenType.KEYWORD and
          token.string == 'else'):
      next_code = tokenutil.SearchExcept(token, TokenType.NON_CODE_TYPES)
      if (next_code.type != TokenType.START_BLOCK and
          (next_code.type != TokenType.KEYWORD or next_code.string != 'if')):
        self._AddContext(EcmaContext.IMPLIED_BLOCK)
        token.metadata.is_implied_block = True

    elif token_type == TokenType.START_PARAMETERS:
      self._AddContext(EcmaContext.PARAMETERS)

    elif token_type == TokenType.END_PARAMETERS:
      return self._PopContextType(EcmaContext.PARAMETERS)

    elif token_type == TokenType.START_BRACKET:
      if (self._last_code and
          self._last_code.type in TokenType.EXPRESSION_ENDER_TYPES):
        self._AddContext(EcmaContext.INDEX)
      else:
        self._AddContext(EcmaContext.ARRAY_LITERAL)

    elif token_type == TokenType.END_BRACKET:
      return self._PopContextType(EcmaContext.INDEX, EcmaContext.ARRAY_LITERAL)

    elif token_type == TokenType.START_BLOCK:
      if (self._last_code.type in (TokenType.END_PAREN,
                                   TokenType.END_PARAMETERS) or
          self._last_code.IsKeyword('else') or
          self._last_code.IsKeyword('do') or
          self._last_code.IsKeyword('try') or
          self._last_code.IsKeyword('finally') or
          (self._last_code.IsOperator(':') and
           self._last_code.metadata.context.type == EcmaContext.CASE_BLOCK)):
        # else, do, try, and finally all might have no () before {.
        # Also, handle the bizzare syntax case 10: {...}.
        self._AddContext(EcmaContext.BLOCK)
      else:
        self._AddContext(EcmaContext.OBJECT_LITERAL)

    elif token_type == TokenType.END_BLOCK:
      context = self._PopContextType(EcmaContext.BLOCK,
                                     EcmaContext.OBJECT_LITERAL)
      if self._context.type == EcmaContext.SWITCH:
        # The end of the block also means the end of the switch statement it
        # applies to.
        return self._PopContext()
      return context

    elif token.IsKeyword('switch'):
      self._AddContext(EcmaContext.SWITCH)

    elif (token_type == TokenType.KEYWORD and
          token.string in ('case', 'default')):
      # Pop up to but not including the switch block.
      while self._context.parent.type != EcmaContext.SWITCH:
        self._PopContext()

    elif token.IsOperator('?'):
      self._AddContext(EcmaContext.TERNARY_TRUE)

    elif token.IsOperator(':'):
      if self._context.type == EcmaContext.OBJECT_LITERAL:
        self._AddContext(EcmaContext.LITERAL_ELEMENT)

      elif self._context.type == EcmaContext.TERNARY_TRUE:
        self._PopContext()
        self._AddContext(EcmaContext.TERNARY_FALSE)

      # Handle nested ternary statements like:
      # foo = bar ? baz ? 1 : 2 : 3
      # When we encounter the second ":" the context is
      # ternary_false > ternary_true > statement > root
      elif (self._context.type == EcmaContext.TERNARY_FALSE and
            self._context.parent.type == EcmaContext.TERNARY_TRUE):
           self._PopContext() # Leave current ternary false context.
           self._PopContext() # Leave current parent ternary true
           self._AddContext(EcmaContext.TERNARY_FALSE)

      elif self._context.parent.type == EcmaContext.SWITCH:
        self._AddContext(EcmaContext.CASE_BLOCK)

    elif token.IsKeyword('var'):
      self._AddContext(EcmaContext.VAR)

    elif token.IsOperator(','):
      while self._context.type not in (EcmaContext.VAR,
                                       EcmaContext.ARRAY_LITERAL,
                                       EcmaContext.OBJECT_LITERAL,
                                       EcmaContext.STATEMENT,
                                       EcmaContext.PARAMETERS,
                                       EcmaContext.GROUP):
        self._PopContext()

    elif token_type == TokenType.SEMICOLON:
      self._EndStatement()