Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(339)

Side by Side Diff: third_party/closure_linter/closure_linter/aliaspass.py

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 """Pass that scans for goog.scope aliases and lint/usage errors."""
17
18 # Allow non-Google copyright
19 # pylint: disable=g-bad-file-header
20
21 __author__ = ('nnaze@google.com (Nathan Naze)')
22
23 from closure_linter import ecmametadatapass
24 from closure_linter import errors
25 from closure_linter import javascripttokens
26 from closure_linter import scopeutil
27 from closure_linter import tokenutil
28 from closure_linter.common import error
29
30
31 # TODO(nnaze): Create a Pass interface and move this class, EcmaMetaDataPass,
32 # and related classes onto it.
33
34
35 def _GetAliasForIdentifier(identifier, alias_map):
36 """Returns the aliased_symbol name for an identifier.
37
38 Example usage:
39 >>> alias_map = {'MyClass': 'goog.foo.MyClass'}
40 >>> _GetAliasForIdentifier('MyClass.prototype.action', alias_map)
41 'goog.foo.MyClass.prototype.action'
42
43 >>> _GetAliasForIdentifier('MyClass.prototype.action', {})
44 None
45
46 Args:
47 identifier: The identifier.
48 alias_map: A dictionary mapping a symbol to an alias.
49
50 Returns:
51 The aliased symbol name or None if not found.
52 """
53 ns = identifier.split('.', 1)[0]
54 aliased_symbol = alias_map.get(ns)
55 if aliased_symbol:
56 return aliased_symbol + identifier[len(ns):]
57
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
73 class AliasPass(object):
74 """Pass to identify goog.scope() usages.
75
76 Identifies goog.scope() usages and finds lint/usage errors. Notes any
77 aliases of symbols in Closurized namespaces (that is, reassignments
78 such as "var MyClass = goog.foo.MyClass;") and annotates identifiers
79 when they're using an alias (so they may be expanded to the full symbol
80 later -- that "MyClass.prototype.action" refers to
81 "goog.foo.MyClass.prototype.action" when expanded.).
82 """
83
84 def __init__(self, closurized_namespaces=None, error_handler=None):
85 """Creates a new pass.
86
87 Args:
88 closurized_namespaces: A set of Closurized namespaces (e.g. 'goog').
89 error_handler: An error handler to report lint errors to.
90 """
91
92 self._error_handler = error_handler
93
94 # If we have namespaces, freeze the set.
95 if closurized_namespaces:
96 closurized_namespaces = frozenset(closurized_namespaces)
97
98 self._closurized_namespaces = closurized_namespaces
99
100 def Process(self, start_token):
101 """Runs the pass on a token stream.
102
103 Args:
104 start_token: The first token in the stream.
105 """
106
107 if start_token is None:
108 return
109
110 # TODO(nnaze): Add more goog.scope usage checks.
111 self._CheckGoogScopeCalls(start_token)
112
113 # If we have closurized namespaces, identify aliased identifiers.
114 if self._closurized_namespaces:
115 context = start_token.metadata.context
116 root_context = context.GetRoot()
117 self._ProcessRootContext(root_context)
118
119 def _CheckGoogScopeCalls(self, start_token):
120 """Check goog.scope calls for lint/usage errors."""
121
122 def IsScopeToken(token):
123 return (token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER and
124 token.string == 'goog.scope')
125
126 # Find all the goog.scope tokens in the file
127 scope_tokens = [t for t in start_token if IsScopeToken(t)]
128
129 for token in scope_tokens:
130 scope_context = token.metadata.context
131
132 if not (scope_context.type == ecmametadatapass.EcmaContext.STATEMENT and
133 scope_context.parent.type == ecmametadatapass.EcmaContext.ROOT):
134 self._MaybeReportError(
135 error.Error(errors.INVALID_USE_OF_GOOG_SCOPE,
136 'goog.scope call not in global scope', token))
137
138 # There should be only one goog.scope reference. Register errors for
139 # every instance after the first.
140 for token in scope_tokens[1:]:
141 self._MaybeReportError(
142 error.Error(errors.EXTRA_GOOG_SCOPE_USAGE,
143 'More than one goog.scope call in file.', token))
144
145 def _MaybeReportError(self, err):
146 """Report an error to the handler (if registered)."""
147 if self._error_handler:
148 self._error_handler.HandleError(err)
149
150 @classmethod
151 def _YieldAllContexts(cls, context):
152 """Yields all contexts that are contained by the given context."""
153 yield context
154 for child_context in context.children:
155 for descendent_child in cls._YieldAllContexts(child_context):
156 yield descendent_child
157
158 @staticmethod
159 def _IsTokenInParentBlock(token, parent_block):
160 """Determines whether the given token is contained by the given block.
161
162 Args:
163 token: A token
164 parent_block: An EcmaContext.
165
166 Returns:
167 Whether the token is in a context that is or is a child of the given
168 parent_block context.
169 """
170 context = token.metadata.context
171
172 while context:
173 if context is parent_block:
174 return True
175 context = context.parent
176
177 return False
178
179 def _ProcessRootContext(self, root_context):
180 """Processes all goog.scope blocks under the root context."""
181
182 assert root_context.type is ecmametadatapass.EcmaContext.ROOT
183
184 # Process aliases in statements in the root scope for goog.module-style
185 # aliases.
186 global_alias_map = {}
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
199
200 # Process each block to find aliases.
201 for context in root_context.children:
202 self._ProcessBlock(context, global_alias_map)
203
204 def _ProcessBlock(self, context, global_alias_map):
205 """Scans a goog.scope block to find aliases and mark alias tokens."""
206 alias_map = global_alias_map.copy()
207
208 # Iterate over every token in the context. Each token points to one
209 # context, but multiple tokens may point to the same context. We only want
210 # to check each context once, so keep track of those we've seen.
211 seen_contexts = set()
212 token = context.start_token
213 while token and self._IsTokenInParentBlock(token, context):
214 token_context = token.metadata.context if token.metadata else None
215
216 # Check to see if this token is an alias.
217 if token_context and token_context not in seen_contexts:
218 seen_contexts.add(token_context)
219
220 # If this is a alias statement in the goog.scope block.
221 if (token_context.type == ecmametadatapass.EcmaContext.VAR and
222 scopeutil.IsGoogScopeBlock(token_context.parent.parent)):
223 match = scopeutil.MatchAlias(token_context)
224
225 # If this is an alias, remember it in the map.
226 if match:
227 alias, symbol = match
228 symbol = _GetAliasForIdentifier(symbol, alias_map) or symbol
229 if scopeutil.IsInClosurizedNamespace(symbol,
230 self._closurized_namespaces):
231 alias_map[alias] = symbol
232
233 # If this token is an identifier that matches an alias,
234 # mark the token as an alias to the original symbol.
235 if (token.type is javascripttokens.JavaScriptTokenType.SIMPLE_LVALUE or
236 token.type is javascripttokens.JavaScriptTokenType.IDENTIFIER):
237 identifier = tokenutil.GetIdentifierForToken(token)
238 if identifier:
239 aliased_symbol = _GetAliasForIdentifier(identifier, alias_map)
240 if aliased_symbol:
241 token.metadata.aliased_symbol = aliased_symbol
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
248 token = token.next # Get next token
OLDNEW
« no previous file with comments | « third_party/closure_linter/closure_linter/__init__.py ('k') | third_party/closure_linter/closure_linter/aliaspass_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698