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