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 |