1#!/usr/bin/env python 2# 3# Copyright 2012 The Closure Linter Authors. All Rights Reserved. 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"""Tools to match goog.scope alias statements.""" 17 18# Allow non-Google copyright 19# pylint: disable=g-bad-file-header 20 21__author__ = ('nnaze@google.com (Nathan Naze)') 22 23import itertools 24 25from closure_linter import ecmametadatapass 26from closure_linter import tokenutil 27from closure_linter.javascripttokens import JavaScriptTokenType 28 29 30 31def IsGoogScopeBlock(context): 32 """Whether the given context is a goog.scope block. 33 34 This function only checks that the block is a function block inside 35 a goog.scope() call. 36 37 TODO(nnaze): Implement goog.scope checks that verify the call is 38 in the root context and contains only a single function literal. 39 40 Args: 41 context: An EcmaContext of type block. 42 43 Returns: 44 Whether the context is a goog.scope block. 45 """ 46 47 if context.type != ecmametadatapass.EcmaContext.BLOCK: 48 return False 49 50 if not _IsFunctionLiteralBlock(context): 51 return False 52 53 # Check that this function is contained by a group 54 # of form "goog.scope(...)". 55 parent = context.parent 56 if parent and parent.type is ecmametadatapass.EcmaContext.GROUP: 57 58 last_code_token = parent.start_token.metadata.last_code 59 60 if (last_code_token and 61 last_code_token.type is JavaScriptTokenType.IDENTIFIER and 62 last_code_token.string == 'goog.scope'): 63 return True 64 65 return False 66 67 68def _IsFunctionLiteralBlock(block_context): 69 """Check if a context is a function literal block (without parameters). 70 71 Example function literal block: 'function() {}' 72 73 Args: 74 block_context: An EcmaContext of type block. 75 76 Returns: 77 Whether this context is a function literal block. 78 """ 79 80 previous_code_tokens_iter = itertools.ifilter( 81 lambda token: token not in JavaScriptTokenType.NON_CODE_TYPES, 82 reversed(block_context.start_token)) 83 84 # Ignore the current token 85 next(previous_code_tokens_iter, None) 86 87 # Grab the previous three tokens and put them in correct order. 88 previous_code_tokens = list(itertools.islice(previous_code_tokens_iter, 3)) 89 previous_code_tokens.reverse() 90 91 # There aren't three previous tokens. 92 if len(previous_code_tokens) is not 3: 93 return False 94 95 # Check that the previous three code tokens are "function ()" 96 previous_code_token_types = [token.type for token in previous_code_tokens] 97 if (previous_code_token_types == [ 98 JavaScriptTokenType.FUNCTION_DECLARATION, 99 JavaScriptTokenType.START_PARAMETERS, 100 JavaScriptTokenType.END_PARAMETERS]): 101 return True 102 103 return False 104 105 106def IsInClosurizedNamespace(symbol, closurized_namespaces): 107 """Match a goog.scope alias. 108 109 Args: 110 symbol: An identifier like 'goog.events.Event'. 111 closurized_namespaces: Iterable of valid Closurized namespaces (strings). 112 113 Returns: 114 True if symbol is an identifier in a Closurized namespace, otherwise False. 115 """ 116 for ns in closurized_namespaces: 117 if symbol.startswith(ns + '.'): 118 return True 119 120 return False 121 122 123def _GetVarAssignmentTokens(context): 124 """Returns the tokens from context if it is a var assignment. 125 126 Args: 127 context: An EcmaContext. 128 129 Returns: 130 If a var assignment, the tokens contained within it w/o the trailing 131 semicolon. 132 """ 133 if context.type != ecmametadatapass.EcmaContext.VAR: 134 return 135 136 # Get the tokens in this statement. 137 if context.start_token and context.end_token: 138 statement_tokens = tokenutil.GetTokenRange(context.start_token, 139 context.end_token) 140 else: 141 return 142 143 # And now just those tokens that are actually code. 144 is_non_code_type = lambda t: t.type not in JavaScriptTokenType.NON_CODE_TYPES 145 code_tokens = filter(is_non_code_type, statement_tokens) 146 147 # Pop off the semicolon if present. 148 if code_tokens and code_tokens[-1].IsType(JavaScriptTokenType.SEMICOLON): 149 code_tokens.pop() 150 151 if len(code_tokens) < 4: 152 return 153 154 if (code_tokens[0].IsKeyword('var') and 155 code_tokens[1].IsType(JavaScriptTokenType.SIMPLE_LVALUE) and 156 code_tokens[2].IsOperator('=')): 157 return code_tokens 158 159 160def MatchAlias(context): 161 """Match an alias statement (some identifier assigned to a variable). 162 163 Example alias: var MyClass = proj.longNamespace.MyClass. 164 165 Args: 166 context: An EcmaContext of type EcmaContext.VAR. 167 168 Returns: 169 If a valid alias, returns a tuple of alias and symbol, otherwise None. 170 """ 171 code_tokens = _GetVarAssignmentTokens(context) 172 if code_tokens is None: 173 return 174 175 if all(tokenutil.IsIdentifierOrDot(t) for t in code_tokens[3:]): 176 # var Foo = bar.Foo; 177 alias, symbol = code_tokens[1], code_tokens[3] 178 # Mark both tokens as an alias definition to not count them as usages. 179 alias.metadata.is_alias_definition = True 180 symbol.metadata.is_alias_definition = True 181 return alias.string, tokenutil.GetIdentifierForToken(symbol) 182 183 184def MatchModuleAlias(context): 185 """Match an alias statement in a goog.module style import. 186 187 Example alias: var MyClass = goog.require('proj.longNamespace.MyClass'). 188 189 Args: 190 context: An EcmaContext. 191 192 Returns: 193 If a valid alias, returns a tuple of alias and symbol, otherwise None. 194 """ 195 code_tokens = _GetVarAssignmentTokens(context) 196 if code_tokens is None: 197 return 198 199 if(code_tokens[3].IsType(JavaScriptTokenType.IDENTIFIER) and 200 code_tokens[3].string == 'goog.require'): 201 # var Foo = goog.require('bar.Foo'); 202 alias = code_tokens[1] 203 symbol = tokenutil.GetStringAfterToken(code_tokens[3]) 204 if symbol: 205 alias.metadata.is_alias_definition = True 206 return alias.string, symbol 207