| OLD | NEW |
| (Empty) |
| 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 | |
| 20 from closure_linter import javascripttokens | |
| 21 from closure_linter import statetracker | |
| 22 from closure_linter import tokenutil | |
| 23 | |
| 24 # Shorthand | |
| 25 Type = javascripttokens.JavaScriptTokenType | |
| 26 | |
| 27 | |
| 28 class 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 | |
| 58 class 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) | |
| OLD | NEW |