| OLD | NEW |
| (Empty) |
| 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 | |
| 23 import itertools | |
| 24 | |
| 25 from closure_linter import ecmametadatapass | |
| 26 from closure_linter import tokenutil | |
| 27 from closure_linter.javascripttokens import JavaScriptTokenType | |
| 28 | |
| 29 | |
| 30 | |
| 31 def 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 | |
| 68 def _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 | |
| 106 def 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 | |
| 123 def _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 | |
| 160 def 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 | |
| 184 def 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 | |
| OLD | NEW |