OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2012 The Closure Linter Authors. All Rights Reserved. | 3 # Copyright 2012 The Closure Linter Authors. All Rights Reserved. |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
5 # you may not use this file except in compliance with 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 | 6 # You may obtain a copy of the License at |
7 # | 7 # |
8 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
9 # | 9 # |
10 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
11 # distributed under the License is distributed on an "AS-IS" BASIS, | 11 # distributed under the License is distributed on an "AS-IS" BASIS, |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
14 # limitations under the License. | 14 # limitations under the License. |
15 | 15 |
16 """Pass that scans for goog.scope aliases and lint/usage errors.""" | 16 """Pass that scans for goog.scope aliases and lint/usage errors.""" |
17 | 17 |
18 # Allow non-Google copyright | 18 # Allow non-Google copyright |
19 # pylint: disable=g-bad-file-header | 19 # pylint: disable=g-bad-file-header |
20 | 20 |
21 __author__ = ('nnaze@google.com (Nathan Naze)') | 21 __author__ = ('nnaze@google.com (Nathan Naze)') |
22 | 22 |
23 import itertools | |
24 | |
25 from closure_linter import ecmametadatapass | 23 from closure_linter import ecmametadatapass |
26 from closure_linter import errors | 24 from closure_linter import errors |
27 from closure_linter import javascripttokens | 25 from closure_linter import javascripttokens |
28 from closure_linter import scopeutil | 26 from closure_linter import scopeutil |
29 from closure_linter import tokenutil | 27 from closure_linter import tokenutil |
30 from closure_linter.common import error | 28 from closure_linter.common import error |
31 | 29 |
32 | 30 |
33 # TODO(nnaze): Create a Pass interface and move this class, EcmaMetaDataPass, | 31 # TODO(nnaze): Create a Pass interface and move this class, EcmaMetaDataPass, |
34 # and related classes onto it. | 32 # and related classes onto it. |
(...skipping 16 matching lines...) Expand all Loading... |
51 | 49 |
52 Returns: | 50 Returns: |
53 The aliased symbol name or None if not found. | 51 The aliased symbol name or None if not found. |
54 """ | 52 """ |
55 ns = identifier.split('.', 1)[0] | 53 ns = identifier.split('.', 1)[0] |
56 aliased_symbol = alias_map.get(ns) | 54 aliased_symbol = alias_map.get(ns) |
57 if aliased_symbol: | 55 if aliased_symbol: |
58 return aliased_symbol + identifier[len(ns):] | 56 return aliased_symbol + identifier[len(ns):] |
59 | 57 |
60 | 58 |
| 59 def _SetTypeAlias(js_type, alias_map): |
| 60 """Updates the alias for identifiers in a type. |
| 61 |
| 62 Args: |
| 63 js_type: A typeannotation.TypeAnnotation instance. |
| 64 alias_map: A dictionary mapping a symbol to an alias. |
| 65 """ |
| 66 aliased_symbol = _GetAliasForIdentifier(js_type.identifier, alias_map) |
| 67 if aliased_symbol: |
| 68 js_type.alias = aliased_symbol |
| 69 for sub_type in js_type.IterTypes(): |
| 70 _SetTypeAlias(sub_type, alias_map) |
| 71 |
| 72 |
61 class AliasPass(object): | 73 class AliasPass(object): |
62 """Pass to identify goog.scope() usages. | 74 """Pass to identify goog.scope() usages. |
63 | 75 |
64 Identifies goog.scope() usages and finds lint/usage errors. Notes any | 76 Identifies goog.scope() usages and finds lint/usage errors. Notes any |
65 aliases of symbols in Closurized namespaces (that is, reassignments | 77 aliases of symbols in Closurized namespaces (that is, reassignments |
66 such as "var MyClass = goog.foo.MyClass;") and annotates identifiers | 78 such as "var MyClass = goog.foo.MyClass;") and annotates identifiers |
67 when they're using an alias (so they may be expanded to the full symbol | 79 when they're using an alias (so they may be expanded to the full symbol |
68 later -- that "MyClass.prototype.action" refers to | 80 later -- that "MyClass.prototype.action" refers to |
69 "goog.foo.MyClass.prototype.action" when expanded.). | 81 "goog.foo.MyClass.prototype.action" when expanded.). |
70 """ | 82 """ |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 return True | 174 return True |
163 context = context.parent | 175 context = context.parent |
164 | 176 |
165 return False | 177 return False |
166 | 178 |
167 def _ProcessRootContext(self, root_context): | 179 def _ProcessRootContext(self, root_context): |
168 """Processes all goog.scope blocks under the root context.""" | 180 """Processes all goog.scope blocks under the root context.""" |
169 | 181 |
170 assert root_context.type is ecmametadatapass.EcmaContext.ROOT | 182 assert root_context.type is ecmametadatapass.EcmaContext.ROOT |
171 | 183 |
172 # Identify all goog.scope blocks. | 184 # Process aliases in statements in the root scope for goog.module-style |
173 goog_scope_blocks = itertools.ifilter( | 185 # aliases. |
174 scopeutil.IsGoogScopeBlock, | 186 global_alias_map = {} |
175 self._YieldAllContexts(root_context)) | 187 for context in root_context.children: |
| 188 if context.type == ecmametadatapass.EcmaContext.STATEMENT: |
| 189 for statement_child in context.children: |
| 190 if statement_child.type == ecmametadatapass.EcmaContext.VAR: |
| 191 match = scopeutil.MatchModuleAlias(statement_child) |
| 192 if match: |
| 193 # goog.require aliases cannot use further aliases, the symbol is |
| 194 # the second part of match, directly. |
| 195 symbol = match[1] |
| 196 if scopeutil.IsInClosurizedNamespace(symbol, |
| 197 self._closurized_namespaces): |
| 198 global_alias_map[match[0]] = symbol |
176 | 199 |
177 # Process each block to find aliases. | 200 # Process each block to find aliases. |
178 for scope_block in goog_scope_blocks: | 201 for context in root_context.children: |
179 self._ProcessGoogScopeBlock(scope_block) | 202 self._ProcessBlock(context, global_alias_map) |
180 | 203 |
181 def _ProcessGoogScopeBlock(self, scope_block): | 204 def _ProcessBlock(self, context, global_alias_map): |
182 """Scans a goog.scope block to find aliases and mark alias tokens.""" | 205 """Scans a goog.scope block to find aliases and mark alias tokens.""" |
| 206 alias_map = global_alias_map.copy() |
183 | 207 |
184 alias_map = dict() | 208 # Iterate over every token in the context. Each token points to one |
185 | |
186 # Iterate over every token in the scope_block. Each token points to one | |
187 # context, but multiple tokens may point to the same context. We only want | 209 # context, but multiple tokens may point to the same context. We only want |
188 # to check each context once, so keep track of those we've seen. | 210 # to check each context once, so keep track of those we've seen. |
189 seen_contexts = set() | 211 seen_contexts = set() |
190 token = scope_block.start_token | 212 token = context.start_token |
191 while token and self._IsTokenInParentBlock(token, scope_block): | 213 while token and self._IsTokenInParentBlock(token, context): |
192 | 214 token_context = token.metadata.context if token.metadata else None |
193 token_context = token.metadata.context | |
194 | 215 |
195 # Check to see if this token is an alias. | 216 # Check to see if this token is an alias. |
196 if token_context not in seen_contexts: | 217 if token_context and token_context not in seen_contexts: |
197 seen_contexts.add(token_context) | 218 seen_contexts.add(token_context) |
198 | 219 |
199 # If this is a alias statement in the goog.scope block. | 220 # If this is a alias statement in the goog.scope block. |
200 if (token_context.type == ecmametadatapass.EcmaContext.VAR and | 221 if (token_context.type == ecmametadatapass.EcmaContext.VAR and |
201 token_context.parent.parent is scope_block): | 222 scopeutil.IsGoogScopeBlock(token_context.parent.parent)): |
202 match = scopeutil.MatchAlias(token_context) | 223 match = scopeutil.MatchAlias(token_context) |
203 | 224 |
204 # If this is an alias, remember it in the map. | 225 # If this is an alias, remember it in the map. |
205 if match: | 226 if match: |
206 alias, symbol = match | 227 alias, symbol = match |
207 symbol = _GetAliasForIdentifier(symbol, alias_map) or symbol | 228 symbol = _GetAliasForIdentifier(symbol, alias_map) or symbol |
208 if scopeutil.IsInClosurizedNamespace(symbol, | 229 if scopeutil.IsInClosurizedNamespace(symbol, |
209 self._closurized_namespaces): | 230 self._closurized_namespaces): |
210 alias_map[alias] = symbol | 231 alias_map[alias] = symbol |
211 | 232 |
212 # If this token is an identifier that matches an alias, | 233 # If this token is an identifier that matches an alias, |
213 # mark the token as an alias to the original symbol. | 234 # mark the token as an alias to the original symbol. |
214 if (token.type is javascripttokens.JavaScriptTokenType.SIMPLE_LVALUE or | 235 if (token.type is javascripttokens.JavaScriptTokenType.SIMPLE_LVALUE or |
215 token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER): | 236 token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER): |
216 identifier = tokenutil.GetIdentifierForToken(token) | 237 identifier = tokenutil.GetIdentifierForToken(token) |
217 if identifier: | 238 if identifier: |
218 aliased_symbol = _GetAliasForIdentifier(identifier, alias_map) | 239 aliased_symbol = _GetAliasForIdentifier(identifier, alias_map) |
219 if aliased_symbol: | 240 if aliased_symbol: |
220 token.metadata.aliased_symbol = aliased_symbol | 241 token.metadata.aliased_symbol = aliased_symbol |
221 | 242 |
| 243 elif token.type == javascripttokens.JavaScriptTokenType.DOC_FLAG: |
| 244 flag = token.attached_object |
| 245 if flag and flag.HasType() and flag.jstype: |
| 246 _SetTypeAlias(flag.jstype, alias_map) |
| 247 |
222 token = token.next # Get next token | 248 token = token.next # Get next token |
OLD | NEW |