1#!/usr/bin/env python
2# Copyright 2008 The Closure Linter Authors. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS-IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Parser for JavaScript files."""
17
18
19
20from closure_linter import javascripttokens
21from closure_linter import statetracker
22from closure_linter import tokenutil
23
24# Shorthand
25Type = javascripttokens.JavaScriptTokenType
26
27
28class JsDocFlag(statetracker.DocFlag):
29  """Javascript doc flag object.
30
31  Attribute:
32    flag_type: param, return, define, type, etc.
33    flag_token: The flag token.
34    type_start_token: The first token specifying the flag JS type,
35      including braces.
36    type_end_token: The last token specifying the flag JS type,
37      including braces.
38    type: The type spec string.
39    jstype: The type spec, a TypeAnnotation instance.
40    name_token: The token specifying the flag name.
41    name: The flag name
42    description_start_token: The first token in the description.
43    description_end_token: The end token in the description.
44    description: The description.
45  """
46
47  # Please keep these lists alphabetized.
48
49  # Some projects use the following extensions to JsDoc.
50  # TODO(robbyw): determine which of these, if any, should be illegal.
51  EXTENDED_DOC = frozenset([
52      'class', 'code', 'desc', 'final', 'hidden', 'inheritDoc', 'link',
53      'meaning', 'provideGoog', 'throws'])
54
55  LEGAL_DOC = EXTENDED_DOC | statetracker.DocFlag.LEGAL_DOC
56
57
58class JavaScriptStateTracker(statetracker.StateTracker):
59  """JavaScript state tracker.
60
61  Inherits from the core EcmaScript StateTracker adding extra state tracking
62  functionality needed for JavaScript.
63  """
64
65  def __init__(self):
66    """Initializes a JavaScript token stream state tracker."""
67    statetracker.StateTracker.__init__(self, JsDocFlag)
68
69  def Reset(self):
70    self._scope_depth = 0
71    self._block_stack = []
72    super(JavaScriptStateTracker, self).Reset()
73
74  def InTopLevel(self):
75    """Compute whether we are at the top level in the class.
76
77    This function call is language specific.  In some languages like
78    JavaScript, a function is top level if it is not inside any parenthesis.
79    In languages such as ActionScript, a function is top level if it is directly
80    within a class.
81
82    Returns:
83      Whether we are at the top level in the class.
84    """
85    return self._scope_depth == self.ParenthesesDepth()
86
87  def InFunction(self):
88    """Returns true if the current token is within a function.
89
90    This js-specific override ignores goog.scope functions.
91
92    Returns:
93      True if the current token is within a function.
94    """
95    return self._scope_depth != self.FunctionDepth()
96
97  def InNonScopeBlock(self):
98    """Compute whether we are nested within a non-goog.scope block.
99
100    Returns:
101      True if the token is not enclosed in a block that does not originate from
102      a goog.scope statement. False otherwise.
103    """
104    return self._scope_depth != self.BlockDepth()
105
106  def GetBlockType(self, token):
107    """Determine the block type given a START_BLOCK token.
108
109    Code blocks come after parameters, keywords  like else, and closing parens.
110
111    Args:
112      token: The current token. Can be assumed to be type START_BLOCK
113    Returns:
114      Code block type for current token.
115    """
116    last_code = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES, reverse=True)
117    if last_code.type in (Type.END_PARAMETERS, Type.END_PAREN,
118                          Type.KEYWORD) and not last_code.IsKeyword('return'):
119      return self.CODE
120    else:
121      return self.OBJECT_LITERAL
122
123  def GetCurrentBlockStart(self):
124    """Gets the start token of current block.
125
126    Returns:
127      Starting token of current block. None if not in block.
128    """
129    if self._block_stack:
130      return self._block_stack[-1]
131    else:
132      return None
133
134  def HandleToken(self, token, last_non_space_token):
135    """Handles the given token and updates state.
136
137    Args:
138      token: The token to handle.
139      last_non_space_token: The last non space token encountered
140    """
141    if token.type == Type.START_BLOCK:
142      self._block_stack.append(token)
143    if token.type == Type.IDENTIFIER and token.string == 'goog.scope':
144      self._scope_depth += 1
145    if token.type == Type.END_BLOCK:
146      start_token = self._block_stack.pop()
147      if tokenutil.GoogScopeOrNoneFromStartBlock(start_token):
148        self._scope_depth -= 1
149    super(JavaScriptStateTracker, self).HandleToken(token,
150                                                    last_non_space_token)
151