OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2008 The Closure Linter Authors. All Rights Reserved. | 3 # Copyright 2008 The Closure Linter Authors. All Rights Reserved. |
4 # | 4 # |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
6 # you may not use this file except in compliance with the License. | 6 # you may not use this file except in compliance with the License. |
7 # You may obtain a copy of the License at | 7 # You may obtain a copy of the License at |
8 # | 8 # |
9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
10 # | 10 # |
11 # Unless required by applicable law or agreed to in writing, software | 11 # Unless required by applicable law or agreed to in writing, software |
12 # distributed under the License is distributed on an "AS-IS" BASIS, | 12 # distributed under the License is distributed on an "AS-IS" BASIS, |
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 # See the License for the specific language governing permissions and | 14 # See the License for the specific language governing permissions and |
15 # limitations under the License. | 15 # limitations under the License. |
16 | 16 |
17 """Core methods for checking EcmaScript files for common style guide violations. | 17 """Core methods for checking EcmaScript files for common style guide violations. |
18 """ | 18 """ |
19 | 19 |
20 __author__ = ('robbyw@google.com (Robert Walker)', | 20 __author__ = ('robbyw@google.com (Robert Walker)', |
21 'ajp@google.com (Andy Perelson)', | 21 'ajp@google.com (Andy Perelson)', |
22 'jacobr@google.com (Jacob Richman)') | 22 'jacobr@google.com (Jacob Richman)') |
23 | 23 |
24 import re | 24 import re |
25 | 25 |
| 26 import gflags as flags |
| 27 |
26 from closure_linter import checkerbase | 28 from closure_linter import checkerbase |
27 from closure_linter import ecmametadatapass | 29 from closure_linter import ecmametadatapass |
28 from closure_linter import error_check | 30 from closure_linter import error_check |
| 31 from closure_linter import errorrules |
29 from closure_linter import errors | 32 from closure_linter import errors |
30 from closure_linter import indentation | 33 from closure_linter import indentation |
| 34 from closure_linter import javascripttokenizer |
31 from closure_linter import javascripttokens | 35 from closure_linter import javascripttokens |
32 from closure_linter import javascripttokenizer | |
33 from closure_linter import statetracker | 36 from closure_linter import statetracker |
34 from closure_linter import tokenutil | 37 from closure_linter import tokenutil |
35 from closure_linter.common import error | 38 from closure_linter.common import error |
36 from closure_linter.common import htmlutil | |
37 from closure_linter.common import lintrunner | |
38 from closure_linter.common import position | 39 from closure_linter.common import position |
39 from closure_linter.common import tokens | 40 |
40 import gflags as flags | |
41 | 41 |
42 FLAGS = flags.FLAGS | 42 FLAGS = flags.FLAGS |
43 flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow') | 43 flags.DEFINE_list('custom_jsdoc_tags', '', 'Extra jsdoc tags to allow') |
44 | 44 |
45 # TODO(robbyw): Check for extra parens on return statements | 45 # TODO(robbyw): Check for extra parens on return statements |
46 # TODO(robbyw): Check for 0px in strings | 46 # TODO(robbyw): Check for 0px in strings |
47 # TODO(robbyw): Ensure inline jsDoc is in {} | 47 # TODO(robbyw): Ensure inline jsDoc is in {} |
48 # TODO(robbyw): Check for valid JS types in parameter docs | 48 # TODO(robbyw): Check for valid JS types in parameter docs |
49 | 49 |
50 # Shorthand | 50 # Shorthand |
51 Context = ecmametadatapass.EcmaContext | 51 Context = ecmametadatapass.EcmaContext |
52 Error = error.Error | 52 Error = error.Error |
53 Modes = javascripttokenizer.JavaScriptModes | 53 Modes = javascripttokenizer.JavaScriptModes |
54 Position = position.Position | 54 Position = position.Position |
55 Rule = error_check.Rule | 55 Rule = error_check.Rule |
56 Type = javascripttokens.JavaScriptTokenType | 56 Type = javascripttokens.JavaScriptTokenType |
57 | 57 |
| 58 |
58 class EcmaScriptLintRules(checkerbase.LintRulesBase): | 59 class EcmaScriptLintRules(checkerbase.LintRulesBase): |
59 """EmcaScript lint style checking rules. | 60 """EmcaScript lint style checking rules. |
60 | 61 |
61 Can be used to find common style errors in JavaScript, ActionScript and other | 62 Can be used to find common style errors in JavaScript, ActionScript and other |
62 Ecma like scripting languages. Style checkers for Ecma scripting languages | 63 Ecma like scripting languages. Style checkers for Ecma scripting languages |
63 should inherit from this style checker. | 64 should inherit from this style checker. |
64 Please do not add any state to EcmaScriptLintRules or to any subclasses. | 65 Please do not add any state to EcmaScriptLintRules or to any subclasses. |
65 | 66 |
66 All state should be added to the StateTracker subclass used for a particular | 67 All state should be added to the StateTracker subclass used for a particular |
67 language. | 68 language. |
68 """ | 69 """ |
69 | 70 |
| 71 # It will be initialized in constructor so the flags are initialized. |
| 72 max_line_length = -1 |
| 73 |
70 # Static constants. | 74 # Static constants. |
71 MAX_LINE_LENGTH = 80 | |
72 | |
73 MISSING_PARAMETER_SPACE = re.compile(r',\S') | 75 MISSING_PARAMETER_SPACE = re.compile(r',\S') |
74 | 76 |
75 EXTRA_SPACE = re.compile('(\(\s|\s\))') | 77 EXTRA_SPACE = re.compile(r'(\(\s|\s\))') |
76 | 78 |
77 ENDS_WITH_SPACE = re.compile('\s$') | 79 ENDS_WITH_SPACE = re.compile(r'\s$') |
78 | 80 |
79 ILLEGAL_TAB = re.compile(r'\t') | 81 ILLEGAL_TAB = re.compile(r'\t') |
80 | 82 |
81 # Regex used to split up complex types to check for invalid use of ? and |. | 83 # Regex used to split up complex types to check for invalid use of ? and |. |
82 TYPE_SPLIT = re.compile(r'[,<>()]') | 84 TYPE_SPLIT = re.compile(r'[,<>()]') |
83 | 85 |
84 # Regex for form of author lines after the @author tag. | 86 # Regex for form of author lines after the @author tag. |
85 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') | 87 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') |
86 | 88 |
87 # Acceptable tokens to remove for line too long testing. | 89 # Acceptable tokens to remove for line too long testing. |
88 LONG_LINE_IGNORE = frozenset(['*', '//', '@see'] + | 90 LONG_LINE_IGNORE = frozenset( |
| 91 ['*', '//', '@see'] + |
89 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) | 92 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) |
90 | 93 |
| 94 JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([ |
| 95 '@param', '@return', '@returns']) |
| 96 |
91 def __init__(self): | 97 def __init__(self): |
92 """Initialize this lint rule object.""" | 98 """Initialize this lint rule object.""" |
93 checkerbase.LintRulesBase.__init__(self) | 99 checkerbase.LintRulesBase.__init__(self) |
| 100 if EcmaScriptLintRules.max_line_length == -1: |
| 101 EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength() |
94 | 102 |
95 def Initialize(self, checker, limited_doc_checks, is_html): | 103 def Initialize(self, checker, limited_doc_checks, is_html): |
96 """Initialize this lint rule object before parsing a new file.""" | 104 """Initialize this lint rule object before parsing a new file.""" |
97 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks, | 105 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks, |
98 is_html) | 106 is_html) |
99 self._indentation = indentation.IndentationRules() | 107 self._indentation = indentation.IndentationRules() |
100 | 108 |
101 def HandleMissingParameterDoc(self, token, param_name): | 109 def HandleMissingParameterDoc(self, token, param_name): |
102 """Handle errors associated with a parameter missing a @param tag.""" | 110 """Handle errors associated with a parameter missing a @param tag.""" |
103 raise TypeError('Abstract method HandleMissingParameterDoc not implemented') | 111 raise TypeError('Abstract method HandleMissingParameterDoc not implemented') |
104 | 112 |
105 def _CheckLineLength(self, last_token, state): | 113 def _CheckLineLength(self, last_token, state): |
106 """Checks whether the line is too long. | 114 """Checks whether the line is too long. |
107 | 115 |
108 Args: | 116 Args: |
109 last_token: The last token in the line. | 117 last_token: The last token in the line. |
| 118 state: parser_state object that indicates the current state in the page |
110 """ | 119 """ |
111 # Start from the last token so that we have the flag object attached to | 120 # Start from the last token so that we have the flag object attached to |
112 # and DOC_FLAG tokens. | 121 # and DOC_FLAG tokens. |
113 line_number = last_token.line_number | 122 line_number = last_token.line_number |
114 token = last_token | 123 token = last_token |
115 | 124 |
116 # Build a representation of the string where spaces indicate potential | 125 # Build a representation of the string where spaces indicate potential |
117 # line-break locations. | 126 # line-break locations. |
118 line = [] | 127 line = [] |
119 while token and token.line_number == line_number: | 128 while token and token.line_number == line_number: |
120 if state.IsTypeToken(token): | 129 if state.IsTypeToken(token): |
121 line.insert(0, 'x' * len(token.string)) | 130 line.insert(0, 'x' * len(token.string)) |
122 elif token.type in (Type.IDENTIFIER, Type.NORMAL): | 131 elif token.type in (Type.IDENTIFIER, Type.NORMAL): |
123 # Dots are acceptable places to wrap. | 132 # Dots are acceptable places to wrap. |
124 line.insert(0, token.string.replace('.', ' ')) | 133 line.insert(0, token.string.replace('.', ' ')) |
125 else: | 134 else: |
126 line.insert(0, token.string) | 135 line.insert(0, token.string) |
127 token = token.previous | 136 token = token.previous |
128 | 137 |
129 line = ''.join(line) | 138 line = ''.join(line) |
130 line = line.rstrip('\n\r\f') | 139 line = line.rstrip('\n\r\f') |
131 try: | 140 try: |
132 length = len(unicode(line, 'utf-8')) | 141 length = len(unicode(line, 'utf-8')) |
133 except: | 142 except (LookupError, UnicodeDecodeError): |
134 # Unknown encoding. The line length may be wrong, as was originally the | 143 # Unknown encoding. The line length may be wrong, as was originally the |
135 # case for utf-8 (see bug 1735846). For now just accept the default | 144 # case for utf-8 (see bug 1735846). For now just accept the default |
136 # length, but as we find problems we can either add test for other | 145 # length, but as we find problems we can either add test for other |
137 # possible encodings or return without an error to protect against | 146 # possible encodings or return without an error to protect against |
138 # false positives at the cost of more false negatives. | 147 # false positives at the cost of more false negatives. |
139 length = len(line) | 148 length = len(line) |
140 | 149 |
141 if length > self.MAX_LINE_LENGTH: | 150 if length > EcmaScriptLintRules.max_line_length: |
142 | 151 |
143 # If the line matches one of the exceptions, then it's ok. | 152 # If the line matches one of the exceptions, then it's ok. |
144 for long_line_regexp in self.GetLongLineExceptions(): | 153 for long_line_regexp in self.GetLongLineExceptions(): |
145 if long_line_regexp.match(last_token.line): | 154 if long_line_regexp.match(last_token.line): |
146 return | 155 return |
147 | 156 |
148 # If the line consists of only one "word", or multiple words but all | 157 # If the line consists of only one "word", or multiple words but all |
149 # except one are ignoreable, then it's ok. | 158 # except one are ignoreable, then it's ok. |
150 parts = set(line.split()) | 159 parts = set(line.split()) |
151 | 160 |
152 # We allow two "words" (type and name) when the line contains @param | 161 # We allow two "words" (type and name) when the line contains @param |
153 max = 1 | 162 max_parts = 1 |
154 if '@param' in parts: | 163 if '@param' in parts: |
155 max = 2 | 164 max_parts = 2 |
156 | 165 |
157 # Custom tags like @requires may have url like descriptions, so ignore | 166 # Custom tags like @requires may have url like descriptions, so ignore |
158 # the tag, similar to how we handle @see. | 167 # the tag, similar to how we handle @see. |
159 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags]) | 168 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags]) |
160 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) > max): | 169 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) |
161 self._HandleError(errors.LINE_TOO_LONG, | 170 > max_parts): |
| 171 self._HandleError( |
| 172 errors.LINE_TOO_LONG, |
162 'Line too long (%d characters).' % len(line), last_token) | 173 'Line too long (%d characters).' % len(line), last_token) |
163 | 174 |
164 def _CheckJsDocType(self, token): | 175 def _CheckJsDocType(self, token): |
165 """Checks the given type for style errors. | 176 """Checks the given type for style errors. |
166 | 177 |
167 Args: | 178 Args: |
168 token: The DOC_FLAG token for the flag whose type to check. | 179 token: The DOC_FLAG token for the flag whose type to check. |
169 """ | 180 """ |
170 flag = token.attached_object | 181 flag = token.attached_object |
171 type = flag.type | 182 flag_type = flag.type |
172 if type and type is not None and not type.isspace(): | 183 if flag_type and flag_type is not None and not flag_type.isspace(): |
173 pieces = self.TYPE_SPLIT.split(type) | 184 pieces = self.TYPE_SPLIT.split(flag_type) |
174 if len(pieces) == 1 and type.count('|') == 1 and ( | 185 if len(pieces) == 1 and flag_type.count('|') == 1 and ( |
175 type.endswith('|null') or type.startswith('null|')): | 186 flag_type.endswith('|null') or flag_type.startswith('null|')): |
176 self._HandleError(errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, | 187 self._HandleError( |
177 'Prefer "?Type" to "Type|null": "%s"' % type, token) | 188 errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, |
| 189 'Prefer "?Type" to "Type|null": "%s"' % flag_type, token) |
178 | 190 |
179 for p in pieces: | 191 # TODO(user): We should do actual parsing of JsDoc types to report an |
180 if p.count('|') and p.count('?'): | 192 # error for wrong usage of '?' and '|' e.g. {?number|string|null} etc. |
181 # TODO(robbyw): We should do actual parsing of JsDoc types. As is, | |
182 # this won't report an error for {number|Array.<string>?}, etc. | |
183 self._HandleError(errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE, | |
184 'JsDoc types cannot contain both "?" and "|": "%s"' % p, token) | |
185 | 193 |
186 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( | 194 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( |
187 flag.type_start_token.type != Type.DOC_START_BRACE or | 195 flag.type_start_token.type != Type.DOC_START_BRACE or |
188 flag.type_end_token.type != Type.DOC_END_BRACE): | 196 flag.type_end_token.type != Type.DOC_END_BRACE): |
189 self._HandleError(errors.MISSING_BRACES_AROUND_TYPE, | 197 self._HandleError( |
| 198 errors.MISSING_BRACES_AROUND_TYPE, |
190 'Type must always be surrounded by curly braces.', token) | 199 'Type must always be surrounded by curly braces.', token) |
191 | 200 |
192 def _CheckForMissingSpaceBeforeToken(self, token): | 201 def _CheckForMissingSpaceBeforeToken(self, token): |
193 """Checks for a missing space at the beginning of a token. | 202 """Checks for a missing space at the beginning of a token. |
194 | 203 |
195 Reports a MISSING_SPACE error if the token does not begin with a space or | 204 Reports a MISSING_SPACE error if the token does not begin with a space or |
196 the previous token doesn't end with a space and the previous token is on the | 205 the previous token doesn't end with a space and the previous token is on the |
197 same line as the token. | 206 same line as the token. |
198 | 207 |
199 Args: | 208 Args: |
200 token: The token being checked | 209 token: The token being checked |
201 """ | 210 """ |
202 # TODO(user): Check if too many spaces? | 211 # TODO(user): Check if too many spaces? |
203 if (len(token.string) == len(token.string.lstrip()) and | 212 if (len(token.string) == len(token.string.lstrip()) and |
204 token.previous and token.line_number == token.previous.line_number and | 213 token.previous and token.line_number == token.previous.line_number and |
205 len(token.previous.string) - len(token.previous.string.rstrip()) == 0): | 214 len(token.previous.string) - len(token.previous.string.rstrip()) == 0): |
206 self._HandleError( | 215 self._HandleError( |
207 errors.MISSING_SPACE, | 216 errors.MISSING_SPACE, |
208 'Missing space before "%s"' % token.string, | 217 'Missing space before "%s"' % token.string, |
209 token, | 218 token, |
210 Position.AtBeginning()) | 219 position=Position.AtBeginning()) |
| 220 |
| 221 def _CheckOperator(self, token): |
| 222 """Checks an operator for spacing and line style. |
| 223 |
| 224 Args: |
| 225 token: The operator token. |
| 226 """ |
| 227 last_code = token.metadata.last_code |
| 228 |
| 229 if not self._ExpectSpaceBeforeOperator(token): |
| 230 if (token.previous and token.previous.type == Type.WHITESPACE and |
| 231 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)): |
| 232 self._HandleError( |
| 233 errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, |
| 234 token.previous, position=Position.All(token.previous.string)) |
| 235 |
| 236 elif (token.previous and |
| 237 not token.previous.IsComment() and |
| 238 token.previous.type in Type.EXPRESSION_ENDER_TYPES): |
| 239 self._HandleError(errors.MISSING_SPACE, |
| 240 'Missing space before "%s"' % token.string, token, |
| 241 position=Position.AtBeginning()) |
| 242 |
| 243 # Check that binary operators are not used to start lines. |
| 244 if ((not last_code or last_code.line_number != token.line_number) and |
| 245 not token.metadata.IsUnaryOperator()): |
| 246 self._HandleError( |
| 247 errors.LINE_STARTS_WITH_OPERATOR, |
| 248 'Binary operator should go on previous line "%s"' % token.string, |
| 249 token) |
211 | 250 |
212 def _ExpectSpaceBeforeOperator(self, token): | 251 def _ExpectSpaceBeforeOperator(self, token): |
213 """Returns whether a space should appear before the given operator token. | 252 """Returns whether a space should appear before the given operator token. |
214 | 253 |
215 Args: | 254 Args: |
216 token: The operator token. | 255 token: The operator token. |
217 | 256 |
218 Returns: | 257 Returns: |
219 Whether there should be a space before the token. | 258 Whether there should be a space before the token. |
220 """ | 259 """ |
(...skipping 19 matching lines...) Expand all Loading... |
240 | 279 |
241 Args: | 280 Args: |
242 token: The current token under consideration | 281 token: The current token under consideration |
243 state: parser_state object that indicates the current state in the page | 282 state: parser_state object that indicates the current state in the page |
244 """ | 283 """ |
245 # Store some convenience variables | 284 # Store some convenience variables |
246 first_in_line = token.IsFirstInLine() | 285 first_in_line = token.IsFirstInLine() |
247 last_in_line = token.IsLastInLine() | 286 last_in_line = token.IsLastInLine() |
248 last_non_space_token = state.GetLastNonSpaceToken() | 287 last_non_space_token = state.GetLastNonSpaceToken() |
249 | 288 |
250 type = token.type | 289 token_type = token.type |
251 | 290 |
252 # Process the line change. | 291 # Process the line change. |
253 if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION): | 292 if not self._is_html and error_check.ShouldCheck(Rule.INDENTATION): |
254 # TODO(robbyw): Support checking indentation in HTML files. | 293 # TODO(robbyw): Support checking indentation in HTML files. |
255 indentation_errors = self._indentation.CheckToken(token, state) | 294 indentation_errors = self._indentation.CheckToken(token, state) |
256 for indentation_error in indentation_errors: | 295 for indentation_error in indentation_errors: |
257 self._HandleError(*indentation_error) | 296 self._HandleError(*indentation_error) |
258 | 297 |
259 if last_in_line: | 298 if last_in_line: |
260 self._CheckLineLength(token, state) | 299 self._CheckLineLength(token, state) |
261 | 300 |
262 if type == Type.PARAMETERS: | 301 if token_type == Type.PARAMETERS: |
263 # Find missing spaces in parameter lists. | 302 # Find missing spaces in parameter lists. |
264 if self.MISSING_PARAMETER_SPACE.search(token.string): | 303 if self.MISSING_PARAMETER_SPACE.search(token.string): |
| 304 fix_data = ', '.join([s.strip() for s in token.string.split(',')]) |
265 self._HandleError(errors.MISSING_SPACE, 'Missing space after ","', | 305 self._HandleError(errors.MISSING_SPACE, 'Missing space after ","', |
266 token) | 306 token, position=None, fix_data=fix_data.strip()) |
267 | 307 |
268 # Find extra spaces at the beginning of parameter lists. Make sure | 308 # Find extra spaces at the beginning of parameter lists. Make sure |
269 # we aren't at the beginning of a continuing multi-line list. | 309 # we aren't at the beginning of a continuing multi-line list. |
270 if not first_in_line: | 310 if not first_in_line: |
271 space_count = len(token.string) - len(token.string.lstrip()) | 311 space_count = len(token.string) - len(token.string.lstrip()) |
272 if space_count: | 312 if space_count: |
273 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', | 313 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', |
274 token, Position(0, space_count)) | 314 token, position=Position(0, space_count)) |
275 | 315 |
276 elif (type == Type.START_BLOCK and | 316 elif (token_type == Type.START_BLOCK and |
277 token.metadata.context.type == Context.BLOCK): | 317 token.metadata.context.type == Context.BLOCK): |
278 self._CheckForMissingSpaceBeforeToken(token) | 318 self._CheckForMissingSpaceBeforeToken(token) |
279 | 319 |
280 elif type == Type.END_BLOCK: | 320 elif token_type == Type.END_BLOCK: |
281 # This check is for object literal end block tokens, but there is no need | 321 # This check is for object literal end block tokens, but there is no need |
282 # to test that condition since a comma at the end of any other kind of | 322 # to test that condition since a comma at the end of any other kind of |
283 # block is undoubtedly a parse error. | 323 # block is undoubtedly a parse error. |
284 last_code = token.metadata.last_code | 324 last_code = token.metadata.last_code |
285 if last_code.IsOperator(','): | 325 if last_code.IsOperator(','): |
286 self._HandleError(errors.COMMA_AT_END_OF_LITERAL, | 326 self._HandleError( |
| 327 errors.COMMA_AT_END_OF_LITERAL, |
287 'Illegal comma at end of object literal', last_code, | 328 'Illegal comma at end of object literal', last_code, |
288 Position.All(last_code.string)) | 329 position=Position.All(last_code.string)) |
289 | 330 |
290 if state.InFunction() and state.IsFunctionClose(): | 331 if state.InFunction() and state.IsFunctionClose(): |
291 is_immediately_called = (token.next and | 332 is_immediately_called = (token.next and |
292 token.next.type == Type.START_PAREN) | 333 token.next.type == Type.START_PAREN) |
293 if state.InTopLevelFunction(): | 334 if state.InTopLevelFunction(): |
294 # When the function was top-level and not immediately called, check | 335 # A semicolons should not be included at the end of a function |
295 # that it's terminated by a semi-colon. | 336 # declaration. |
296 if state.InAssignedFunction(): | 337 if not state.InAssignedFunction(): |
297 if not is_immediately_called and (last_in_line or | |
298 not token.next.type == Type.SEMICOLON): | |
299 self._HandleError(errors.MISSING_SEMICOLON_AFTER_FUNCTION, | |
300 'Missing semicolon after function assigned to a variable', | |
301 token, Position.AtEnd(token.string)) | |
302 else: | |
303 if not last_in_line and token.next.type == Type.SEMICOLON: | 338 if not last_in_line and token.next.type == Type.SEMICOLON: |
304 self._HandleError(errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, | 339 self._HandleError( |
| 340 errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, |
305 'Illegal semicolon after function declaration', | 341 'Illegal semicolon after function declaration', |
306 token.next, Position.All(token.next.string)) | 342 token.next, position=Position.All(token.next.string)) |
307 | 343 |
308 if (state.InInterfaceMethod() and last_code.type != Type.START_BLOCK): | 344 # A semicolon should be included at the end of a function expression |
| 345 # that is not immediately called. |
| 346 if state.InAssignedFunction(): |
| 347 if not is_immediately_called and ( |
| 348 last_in_line or token.next.type != Type.SEMICOLON): |
| 349 self._HandleError( |
| 350 errors.MISSING_SEMICOLON_AFTER_FUNCTION, |
| 351 'Missing semicolon after function assigned to a variable', |
| 352 token, position=Position.AtEnd(token.string)) |
| 353 |
| 354 if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK: |
309 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, | 355 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, |
310 'Interface methods cannot contain code', last_code) | 356 'Interface methods cannot contain code', last_code) |
311 | 357 |
312 elif (state.IsBlockClose() and | 358 elif (state.IsBlockClose() and |
313 token.next and token.next.type == Type.SEMICOLON): | 359 token.next and token.next.type == Type.SEMICOLON): |
314 self._HandleError(errors.REDUNDANT_SEMICOLON, | 360 if (last_code.metadata.context.parent.type != Context.OBJECT_LITERAL |
315 'No semicolon is required to end a code block', | 361 and last_code.metadata.context.type != Context.OBJECT_LITERAL): |
316 token.next, Position.All(token.next.string)) | 362 self._HandleError( |
| 363 errors.REDUNDANT_SEMICOLON, |
| 364 'No semicolon is required to end a code block', |
| 365 token.next, position=Position.All(token.next.string)) |
317 | 366 |
318 elif type == Type.SEMICOLON: | 367 elif token_type == Type.SEMICOLON: |
319 if token.previous and token.previous.type == Type.WHITESPACE: | 368 if token.previous and token.previous.type == Type.WHITESPACE: |
320 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ";"', | 369 self._HandleError( |
321 token.previous, Position.All(token.previous.string)) | 370 errors.EXTRA_SPACE, 'Extra space before ";"', |
| 371 token.previous, position=Position.All(token.previous.string)) |
322 | 372 |
323 if token.next and token.next.line_number == token.line_number: | 373 if token.next and token.next.line_number == token.line_number: |
324 if token.metadata.context.type != Context.FOR_GROUP_BLOCK: | 374 if token.metadata.context.type != Context.FOR_GROUP_BLOCK: |
325 # TODO(robbyw): Error about no multi-statement lines. | 375 # TODO(robbyw): Error about no multi-statement lines. |
326 pass | 376 pass |
327 | 377 |
328 elif token.next.type not in ( | 378 elif token.next.type not in ( |
329 Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN): | 379 Type.WHITESPACE, Type.SEMICOLON, Type.END_PAREN): |
330 self._HandleError(errors.MISSING_SPACE, | 380 self._HandleError( |
| 381 errors.MISSING_SPACE, |
331 'Missing space after ";" in for statement', | 382 'Missing space after ";" in for statement', |
332 token.next, | 383 token.next, |
333 Position.AtBeginning()) | 384 position=Position.AtBeginning()) |
334 | 385 |
335 last_code = token.metadata.last_code | 386 last_code = token.metadata.last_code |
336 if last_code and last_code.type == Type.SEMICOLON: | 387 if last_code and last_code.type == Type.SEMICOLON: |
337 # Allow a single double semi colon in for loops for cases like: | 388 # Allow a single double semi colon in for loops for cases like: |
338 # for (;;) { }. | 389 # for (;;) { }. |
339 # NOTE(user): This is not a perfect check, and will not throw an error | 390 # NOTE(user): This is not a perfect check, and will not throw an error |
340 # for cases like: for (var i = 0;; i < n; i++) {}, but then your code | 391 # for cases like: for (var i = 0;; i < n; i++) {}, but then your code |
341 # probably won't work either. | 392 # probably won't work either. |
342 for_token = tokenutil.CustomSearch(last_code, | 393 for_token = tokenutil.CustomSearch( |
| 394 last_code, |
343 lambda token: token.type == Type.KEYWORD and token.string == 'for', | 395 lambda token: token.type == Type.KEYWORD and token.string == 'for', |
344 end_func=lambda token: token.type == Type.SEMICOLON, | 396 end_func=lambda token: token.type == Type.SEMICOLON, |
345 distance=None, | 397 distance=None, |
346 reverse=True) | 398 reverse=True) |
347 | 399 |
348 if not for_token: | 400 if not for_token: |
349 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', | 401 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', |
350 token, Position.All(token.string)) | 402 token, position=Position.All(token.string)) |
351 | 403 |
352 elif type == Type.START_PAREN: | 404 elif token_type == Type.START_PAREN: |
353 if token.previous and token.previous.type == Type.KEYWORD: | 405 if token.previous and token.previous.type == Type.KEYWORD: |
354 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', | 406 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', |
355 token, Position.AtBeginning()) | 407 token, position=Position.AtBeginning()) |
356 elif token.previous and token.previous.type == Type.WHITESPACE: | 408 elif token.previous and token.previous.type == Type.WHITESPACE: |
357 before_space = token.previous.previous | 409 before_space = token.previous.previous |
358 if (before_space and before_space.line_number == token.line_number and | 410 if (before_space and before_space.line_number == token.line_number and |
359 before_space.type == Type.IDENTIFIER): | 411 before_space.type == Type.IDENTIFIER): |
360 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "("', | 412 self._HandleError( |
361 token.previous, Position.All(token.previous.string)) | 413 errors.EXTRA_SPACE, 'Extra space before "("', |
| 414 token.previous, position=Position.All(token.previous.string)) |
362 | 415 |
363 elif type == Type.START_BRACKET: | 416 elif token_type == Type.START_BRACKET: |
364 self._HandleStartBracket(token, last_non_space_token) | 417 self._HandleStartBracket(token, last_non_space_token) |
365 elif type in (Type.END_PAREN, Type.END_BRACKET): | 418 elif token_type in (Type.END_PAREN, Type.END_BRACKET): |
366 # Ensure there is no space before closing parentheses, except when | 419 # Ensure there is no space before closing parentheses, except when |
367 # it's in a for statement with an omitted section, or when it's at the | 420 # it's in a for statement with an omitted section, or when it's at the |
368 # beginning of a line. | 421 # beginning of a line. |
369 if (token.previous and token.previous.type == Type.WHITESPACE and | 422 if (token.previous and token.previous.type == Type.WHITESPACE and |
370 not token.previous.IsFirstInLine() and | 423 not token.previous.IsFirstInLine() and |
371 not (last_non_space_token and last_non_space_token.line_number == | 424 not (last_non_space_token and last_non_space_token.line_number == |
372 token.line_number and | 425 token.line_number and |
373 last_non_space_token.type == Type.SEMICOLON)): | 426 last_non_space_token.type == Type.SEMICOLON)): |
374 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "%s"' % | 427 self._HandleError( |
375 token.string, token.previous, Position.All(token.previous.string)) | 428 errors.EXTRA_SPACE, 'Extra space before "%s"' % |
| 429 token.string, token.previous, |
| 430 position=Position.All(token.previous.string)) |
376 | 431 |
377 if token.type == Type.END_BRACKET: | 432 if token.type == Type.END_BRACKET: |
378 last_code = token.metadata.last_code | 433 last_code = token.metadata.last_code |
379 if last_code.IsOperator(','): | 434 if last_code.IsOperator(','): |
380 self._HandleError(errors.COMMA_AT_END_OF_LITERAL, | 435 self._HandleError( |
| 436 errors.COMMA_AT_END_OF_LITERAL, |
381 'Illegal comma at end of array literal', last_code, | 437 'Illegal comma at end of array literal', last_code, |
382 Position.All(last_code.string)) | 438 position=Position.All(last_code.string)) |
383 | 439 |
384 elif type == Type.WHITESPACE: | 440 elif token_type == Type.WHITESPACE: |
385 if self.ILLEGAL_TAB.search(token.string): | 441 if self.ILLEGAL_TAB.search(token.string): |
386 if token.IsFirstInLine(): | 442 if token.IsFirstInLine(): |
387 if token.next: | 443 if token.next: |
388 self._HandleError(errors.ILLEGAL_TAB, | 444 self._HandleError( |
| 445 errors.ILLEGAL_TAB, |
389 'Illegal tab in whitespace before "%s"' % token.next.string, | 446 'Illegal tab in whitespace before "%s"' % token.next.string, |
390 token, Position.All(token.string)) | 447 token, position=Position.All(token.string)) |
391 else: | 448 else: |
392 self._HandleError(errors.ILLEGAL_TAB, | 449 self._HandleError( |
| 450 errors.ILLEGAL_TAB, |
393 'Illegal tab in whitespace', | 451 'Illegal tab in whitespace', |
394 token, Position.All(token.string)) | 452 token, position=Position.All(token.string)) |
395 else: | 453 else: |
396 self._HandleError(errors.ILLEGAL_TAB, | 454 self._HandleError( |
| 455 errors.ILLEGAL_TAB, |
397 'Illegal tab in whitespace after "%s"' % token.previous.string, | 456 'Illegal tab in whitespace after "%s"' % token.previous.string, |
398 token, Position.All(token.string)) | 457 token, position=Position.All(token.string)) |
399 | 458 |
400 # Check whitespace length if it's not the first token of the line and | 459 # Check whitespace length if it's not the first token of the line and |
401 # if it's not immediately before a comment. | 460 # if it's not immediately before a comment. |
402 if last_in_line: | 461 if last_in_line: |
403 # Check for extra whitespace at the end of a line. | 462 # Check for extra whitespace at the end of a line. |
404 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', | 463 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', |
405 token, Position.All(token.string)) | 464 token, position=Position.All(token.string)) |
406 elif not first_in_line and not token.next.IsComment(): | 465 elif not first_in_line and not token.next.IsComment(): |
407 if token.length > 1: | 466 if token.length > 1: |
408 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "%s"' % | 467 self._HandleError( |
| 468 errors.EXTRA_SPACE, 'Extra space after "%s"' % |
409 token.previous.string, token, | 469 token.previous.string, token, |
410 Position(1, len(token.string) - 1)) | 470 position=Position(1, len(token.string) - 1)) |
411 | 471 |
412 elif type == Type.OPERATOR: | 472 elif token_type == Type.OPERATOR: |
413 last_code = token.metadata.last_code | 473 self._CheckOperator(token) |
414 | 474 elif token_type == Type.DOC_FLAG: |
415 if not self._ExpectSpaceBeforeOperator(token): | |
416 if (token.previous and token.previous.type == Type.WHITESPACE and | |
417 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)): | |
418 self._HandleError(errors.EXTRA_SPACE, | |
419 'Extra space before "%s"' % token.string, token.previous, | |
420 Position.All(token.previous.string)) | |
421 | |
422 elif (token.previous and | |
423 not token.previous.IsComment() and | |
424 token.previous.type in Type.EXPRESSION_ENDER_TYPES): | |
425 self._HandleError(errors.MISSING_SPACE, | |
426 'Missing space before "%s"' % token.string, token, | |
427 Position.AtBeginning()) | |
428 | |
429 # Check that binary operators are not used to start lines. | |
430 if ((not last_code or last_code.line_number != token.line_number) and | |
431 not token.metadata.IsUnaryOperator()): | |
432 self._HandleError(errors.LINE_STARTS_WITH_OPERATOR, | |
433 'Binary operator should go on previous line "%s"' % token.string, | |
434 token) | |
435 | |
436 elif type == Type.DOC_FLAG: | |
437 flag = token.attached_object | 475 flag = token.attached_object |
438 | 476 |
439 if flag.flag_type == 'bug': | 477 if flag.flag_type == 'bug': |
440 # TODO(robbyw): Check for exactly 1 space on the left. | 478 # TODO(robbyw): Check for exactly 1 space on the left. |
441 string = token.next.string.lstrip() | 479 string = token.next.string.lstrip() |
442 string = string.split(' ', 1)[0] | 480 string = string.split(' ', 1)[0] |
443 | 481 |
444 if not string.isdigit(): | 482 if not string.isdigit(): |
445 self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG, | 483 self._HandleError(errors.NO_BUG_NUMBER_AFTER_BUG_TAG, |
446 '@bug should be followed by a bug number', token) | 484 '@bug should be followed by a bug number', token) |
447 | 485 |
448 elif flag.flag_type == 'suppress': | 486 elif flag.flag_type == 'suppress': |
449 if flag.type is None: | 487 if flag.type is None: |
450 # A syntactically invalid suppress tag will get tokenized as a normal | 488 # A syntactically invalid suppress tag will get tokenized as a normal |
451 # flag, indicating an error. | 489 # flag, indicating an error. |
452 self._HandleError(errors.INCORRECT_SUPPRESS_SYNTAX, | 490 self._HandleError( |
| 491 errors.INCORRECT_SUPPRESS_SYNTAX, |
453 'Invalid suppress syntax: should be @suppress {errortype}. ' | 492 'Invalid suppress syntax: should be @suppress {errortype}. ' |
454 'Spaces matter.', token) | 493 'Spaces matter.', token) |
455 else: | 494 else: |
456 for suppress_type in flag.type.split('|'): | 495 for suppress_type in re.split(r'\||,', flag.type): |
457 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: | 496 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: |
458 self._HandleError(errors.INVALID_SUPPRESS_TYPE, | 497 self._HandleError( |
459 'Invalid suppression type: %s' % suppress_type, | 498 errors.INVALID_SUPPRESS_TYPE, |
460 token) | 499 'Invalid suppression type: %s' % suppress_type, token) |
461 | 500 |
462 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and | 501 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and |
463 flag.flag_type == 'author'): | 502 flag.flag_type == 'author'): |
464 # TODO(user): In non strict mode check the author tag for as much as | 503 # TODO(user): In non strict mode check the author tag for as much as |
465 # it exists, though the full form checked below isn't required. | 504 # it exists, though the full form checked below isn't required. |
466 string = token.next.string | 505 string = token.next.string |
467 result = self.AUTHOR_SPEC.match(string) | 506 result = self.AUTHOR_SPEC.match(string) |
468 if not result: | 507 if not result: |
469 self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION, | 508 self._HandleError(errors.INVALID_AUTHOR_TAG_DESCRIPTION, |
470 'Author tag line should be of the form: ' | 509 'Author tag line should be of the form: ' |
471 '@author foo@somewhere.com (Your Name)', | 510 '@author foo@somewhere.com (Your Name)', |
472 token.next) | 511 token.next) |
473 else: | 512 else: |
474 # Check spacing between email address and name. Do this before | 513 # Check spacing between email address and name. Do this before |
475 # checking earlier spacing so positions are easier to calculate for | 514 # checking earlier spacing so positions are easier to calculate for |
476 # autofixing. | 515 # autofixing. |
477 num_spaces = len(result.group(2)) | 516 num_spaces = len(result.group(2)) |
478 if num_spaces < 1: | 517 if num_spaces < 1: |
479 self._HandleError(errors.MISSING_SPACE, | 518 self._HandleError(errors.MISSING_SPACE, |
480 'Missing space after email address', | 519 'Missing space after email address', |
481 token.next, Position(result.start(2), 0)) | 520 token.next, position=Position(result.start(2), 0)) |
482 elif num_spaces > 1: | 521 elif num_spaces > 1: |
483 self._HandleError(errors.EXTRA_SPACE, | 522 self._HandleError( |
484 'Extra space after email address', | 523 errors.EXTRA_SPACE, 'Extra space after email address', |
485 token.next, | 524 token.next, |
486 Position(result.start(2) + 1, num_spaces - 1)) | 525 position=Position(result.start(2) + 1, num_spaces - 1)) |
487 | 526 |
488 # Check for extra spaces before email address. Can't be too few, if | 527 # Check for extra spaces before email address. Can't be too few, if |
489 # not at least one we wouldn't match @author tag. | 528 # not at least one we wouldn't match @author tag. |
490 num_spaces = len(result.group(1)) | 529 num_spaces = len(result.group(1)) |
491 if num_spaces > 1: | 530 if num_spaces > 1: |
492 self._HandleError(errors.EXTRA_SPACE, | 531 self._HandleError(errors.EXTRA_SPACE, |
493 'Extra space before email address', | 532 'Extra space before email address', |
494 token.next, Position(1, num_spaces - 1)) | 533 token.next, position=Position(1, num_spaces - 1)) |
495 | 534 |
496 elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and | 535 elif (flag.flag_type in state.GetDocFlag().HAS_DESCRIPTION and |
497 not self._limited_doc_checks): | 536 not self._limited_doc_checks): |
498 if flag.flag_type == 'param': | 537 if flag.flag_type == 'param': |
499 if flag.name is None: | 538 if flag.name is None: |
500 self._HandleError(errors.MISSING_JSDOC_PARAM_NAME, | 539 self._HandleError(errors.MISSING_JSDOC_PARAM_NAME, |
501 'Missing name in @param tag', token) | 540 'Missing name in @param tag', token) |
502 | 541 |
503 if not flag.description or flag.description is None: | 542 if not flag.description or flag.description is None: |
504 flag_name = token.type | 543 flag_name = token.type |
505 if 'name' in token.values: | 544 if 'name' in token.values: |
506 flag_name = '@' + token.values['name'] | 545 flag_name = '@' + token.values['name'] |
507 self._HandleError(errors.MISSING_JSDOC_TAG_DESCRIPTION, | 546 |
508 'Missing description in %s tag' % flag_name, token) | 547 if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED: |
| 548 self._HandleError( |
| 549 errors.MISSING_JSDOC_TAG_DESCRIPTION, |
| 550 'Missing description in %s tag' % flag_name, token) |
509 else: | 551 else: |
510 self._CheckForMissingSpaceBeforeToken(flag.description_start_token) | 552 self._CheckForMissingSpaceBeforeToken(flag.description_start_token) |
511 | 553 |
512 # We want punctuation to be inside of any tags ending a description, | |
513 # so strip tags before checking description. See bug 1127192. Note | |
514 # that depending on how lines break, the real description end token | |
515 # may consist only of stripped html and the effective end token can | |
516 # be different. | |
517 end_token = flag.description_end_token | |
518 end_string = htmlutil.StripTags(end_token.string).strip() | |
519 while (end_string == '' and not | |
520 end_token.type in Type.FLAG_ENDING_TYPES): | |
521 end_token = end_token.previous | |
522 if end_token.type in Type.FLAG_DESCRIPTION_TYPES: | |
523 end_string = htmlutil.StripTags(end_token.string).rstrip() | |
524 | |
525 if not (end_string.endswith('.') or end_string.endswith('?') or | |
526 end_string.endswith('!')): | |
527 # Find the position for the missing punctuation, inside of any html | |
528 # tags. | |
529 desc_str = end_token.string.rstrip() | |
530 while desc_str.endswith('>'): | |
531 start_tag_index = desc_str.rfind('<') | |
532 if start_tag_index < 0: | |
533 break | |
534 desc_str = desc_str[:start_tag_index].rstrip() | |
535 end_position = Position(len(desc_str), 0) | |
536 | |
537 self._HandleError( | |
538 errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER, | |
539 ('%s descriptions must end with valid punctuation such as a ' | |
540 'period.' % token.string), | |
541 end_token, end_position) | |
542 | |
543 if flag.flag_type in state.GetDocFlag().HAS_TYPE: | 554 if flag.flag_type in state.GetDocFlag().HAS_TYPE: |
544 if flag.type_start_token is not None: | 555 if flag.type_start_token is not None: |
545 self._CheckForMissingSpaceBeforeToken( | 556 self._CheckForMissingSpaceBeforeToken( |
546 token.attached_object.type_start_token) | 557 token.attached_object.type_start_token) |
547 | 558 |
548 if flag.type and flag.type != '' and not flag.type.isspace(): | 559 if flag.type and not flag.type.isspace(): |
549 self._CheckJsDocType(token) | 560 self._CheckJsDocType(token) |
550 | 561 |
551 if type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): | 562 if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): |
552 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and | 563 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and |
553 token.values['name'] not in FLAGS.custom_jsdoc_tags): | 564 token.values['name'] not in FLAGS.custom_jsdoc_tags): |
554 self._HandleError(errors.INVALID_JSDOC_TAG, | 565 self._HandleError( |
555 'Invalid JsDoc tag: %s' % token.values['name'], token) | 566 errors.INVALID_JSDOC_TAG, |
| 567 'Invalid JsDoc tag: %s' % token.values['name'], token) |
556 | 568 |
557 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and | 569 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and |
558 token.values['name'] == 'inheritDoc' and | 570 token.values['name'] == 'inheritDoc' and |
559 type == Type.DOC_INLINE_FLAG): | 571 token_type == Type.DOC_INLINE_FLAG): |
560 self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC, | 572 self._HandleError(errors.UNNECESSARY_BRACES_AROUND_INHERIT_DOC, |
561 'Unnecessary braces around @inheritDoc', | 573 'Unnecessary braces around @inheritDoc', |
562 token) | 574 token) |
563 | 575 |
564 elif type == Type.SIMPLE_LVALUE: | 576 elif token_type == Type.SIMPLE_LVALUE: |
565 identifier = token.values['identifier'] | 577 identifier = token.values['identifier'] |
566 | 578 |
567 if ((not state.InFunction() or state.InConstructor()) and | 579 if ((not state.InFunction() or state.InConstructor()) and |
568 not state.InParentheses() and not state.InObjectLiteralDescendant()): | 580 state.InTopLevel() and not state.InObjectLiteralDescendant()): |
569 jsdoc = state.GetDocComment() | 581 jsdoc = state.GetDocComment() |
570 if not state.HasDocComment(identifier): | 582 if not state.HasDocComment(identifier): |
571 # Only test for documentation on identifiers with .s in them to | 583 # Only test for documentation on identifiers with .s in them to |
572 # avoid checking things like simple variables. We don't require | 584 # avoid checking things like simple variables. We don't require |
573 # documenting assignments to .prototype itself (bug 1880803). | 585 # documenting assignments to .prototype itself (bug 1880803). |
574 if (not state.InConstructor() and | 586 if (not state.InConstructor() and |
575 identifier.find('.') != -1 and not | 587 identifier.find('.') != -1 and not |
576 identifier.endswith('.prototype') and not | 588 identifier.endswith('.prototype') and not |
577 self._limited_doc_checks): | 589 self._limited_doc_checks): |
578 comment = state.GetLastComment() | 590 comment = state.GetLastComment() |
579 if not (comment and comment.lower().count('jsdoc inherited')): | 591 if not (comment and comment.lower().count('jsdoc inherited')): |
580 self._HandleError(errors.MISSING_MEMBER_DOCUMENTATION, | 592 self._HandleError( |
| 593 errors.MISSING_MEMBER_DOCUMENTATION, |
581 "No docs found for member '%s'" % identifier, | 594 "No docs found for member '%s'" % identifier, |
582 token); | 595 token) |
583 elif jsdoc and (not state.InConstructor() or | 596 elif jsdoc and (not state.InConstructor() or |
584 identifier.startswith('this.')): | 597 identifier.startswith('this.')): |
585 # We are at the top level and the function/member is documented. | 598 # We are at the top level and the function/member is documented. |
586 if identifier.endswith('_') and not identifier.endswith('__'): | 599 if identifier.endswith('_') and not identifier.endswith('__'): |
587 # Can have a private class which inherits documentation from a | 600 # Can have a private class which inherits documentation from a |
588 # public superclass. | 601 # public superclass. |
589 # | 602 # |
590 # @inheritDoc is deprecated in favor of using @override, and they | 603 # @inheritDoc is deprecated in favor of using @override, and they |
591 if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor') | 604 if (jsdoc.HasFlag('override') and not jsdoc.HasFlag('constructor') |
592 and not ('accessControls' in jsdoc.suppressions)): | 605 and ('accessControls' not in jsdoc.suppressions)): |
593 self._HandleError(errors.INVALID_OVERRIDE_PRIVATE, | 606 self._HandleError( |
| 607 errors.INVALID_OVERRIDE_PRIVATE, |
594 '%s should not override a private member.' % identifier, | 608 '%s should not override a private member.' % identifier, |
595 jsdoc.GetFlag('override').flag_token) | 609 jsdoc.GetFlag('override').flag_token) |
596 if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor') | 610 if (jsdoc.HasFlag('inheritDoc') and not jsdoc.HasFlag('constructor') |
597 and not ('accessControls' in jsdoc.suppressions)): | 611 and ('accessControls' not in jsdoc.suppressions)): |
598 self._HandleError(errors.INVALID_INHERIT_DOC_PRIVATE, | 612 self._HandleError( |
| 613 errors.INVALID_INHERIT_DOC_PRIVATE, |
599 '%s should not inherit from a private member.' % identifier, | 614 '%s should not inherit from a private member.' % identifier, |
600 jsdoc.GetFlag('inheritDoc').flag_token) | 615 jsdoc.GetFlag('inheritDoc').flag_token) |
601 if (not jsdoc.HasFlag('private') and | 616 if (not jsdoc.HasFlag('private') and |
602 not ('underscore' in jsdoc.suppressions) and not | 617 ('underscore' not in jsdoc.suppressions) and not |
603 ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and | 618 ((jsdoc.HasFlag('inheritDoc') or jsdoc.HasFlag('override')) and |
604 ('accessControls' in jsdoc.suppressions))): | 619 ('accessControls' in jsdoc.suppressions))): |
605 self._HandleError(errors.MISSING_PRIVATE, | 620 self._HandleError( |
| 621 errors.MISSING_PRIVATE, |
606 'Member "%s" must have @private JsDoc.' % | 622 'Member "%s" must have @private JsDoc.' % |
607 identifier, token) | 623 identifier, token) |
608 if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions: | 624 if jsdoc.HasFlag('private') and 'underscore' in jsdoc.suppressions: |
609 self._HandleError(errors.UNNECESSARY_SUPPRESS, | 625 self._HandleError( |
| 626 errors.UNNECESSARY_SUPPRESS, |
610 '@suppress {underscore} is not necessary with @private', | 627 '@suppress {underscore} is not necessary with @private', |
611 jsdoc.suppressions['underscore']) | 628 jsdoc.suppressions['underscore']) |
612 elif (jsdoc.HasFlag('private') and | 629 elif (jsdoc.HasFlag('private') and |
613 not self.InExplicitlyTypedLanguage()): | 630 not self.InExplicitlyTypedLanguage()): |
614 # It is convention to hide public fields in some ECMA | 631 # It is convention to hide public fields in some ECMA |
615 # implementations from documentation using the @private tag. | 632 # implementations from documentation using the @private tag. |
616 self._HandleError(errors.EXTRA_PRIVATE, | 633 self._HandleError( |
| 634 errors.EXTRA_PRIVATE, |
617 'Member "%s" must not have @private JsDoc' % | 635 'Member "%s" must not have @private JsDoc' % |
618 identifier, token) | 636 identifier, token) |
619 | 637 |
620 # These flags are only legal on localizable message definitions; | 638 # These flags are only legal on localizable message definitions; |
621 # such variables always begin with the prefix MSG_. | 639 # such variables always begin with the prefix MSG_. |
622 for f in ('desc', 'hidden', 'meaning'): | 640 for f in ('desc', 'hidden', 'meaning'): |
623 if (jsdoc.HasFlag(f) | 641 if (jsdoc.HasFlag(f) |
624 and not identifier.startswith('MSG_') | 642 and not identifier.startswith('MSG_') |
625 and identifier.find('.MSG_') == -1): | 643 and identifier.find('.MSG_') == -1): |
626 self._HandleError(errors.INVALID_USE_OF_DESC_TAG, | 644 self._HandleError( |
| 645 errors.INVALID_USE_OF_DESC_TAG, |
627 'Member "%s" should not have @%s JsDoc' % (identifier, f), | 646 'Member "%s" should not have @%s JsDoc' % (identifier, f), |
628 token) | 647 token) |
629 | 648 |
630 # Check for illegaly assigning live objects as prototype property values. | 649 # Check for illegaly assigning live objects as prototype property values. |
631 index = identifier.find('.prototype.') | 650 index = identifier.find('.prototype.') |
632 # Ignore anything with additional .s after the prototype. | 651 # Ignore anything with additional .s after the prototype. |
633 if index != -1 and identifier.find('.', index + 11) == -1: | 652 if index != -1 and identifier.find('.', index + 11) == -1: |
634 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) | 653 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
635 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) | 654 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) |
636 if next_code and ( | 655 if next_code and ( |
637 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or | 656 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or |
638 next_code.IsOperator('new')): | 657 next_code.IsOperator('new')): |
639 self._HandleError(errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE, | 658 self._HandleError( |
| 659 errors.ILLEGAL_PROTOTYPE_MEMBER_VALUE, |
640 'Member %s cannot have a non-primitive value' % identifier, | 660 'Member %s cannot have a non-primitive value' % identifier, |
641 token) | 661 token) |
642 | 662 |
643 elif type == Type.END_PARAMETERS: | 663 elif token_type == Type.END_PARAMETERS: |
644 # Find extra space at the end of parameter lists. We check the token | 664 # Find extra space at the end of parameter lists. We check the token |
645 # prior to the current one when it is a closing paren. | 665 # prior to the current one when it is a closing paren. |
646 if (token.previous and token.previous.type == Type.PARAMETERS | 666 if (token.previous and token.previous.type == Type.PARAMETERS |
647 and self.ENDS_WITH_SPACE.search(token.previous.string)): | 667 and self.ENDS_WITH_SPACE.search(token.previous.string)): |
648 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"', | 668 self._HandleError(errors.EXTRA_SPACE, 'Extra space before ")"', |
649 token.previous) | 669 token.previous) |
650 | 670 |
651 jsdoc = state.GetDocComment() | 671 jsdoc = state.GetDocComment() |
652 if state.GetFunction().is_interface: | 672 if state.GetFunction().is_interface: |
653 if token.previous and token.previous.type == Type.PARAMETERS: | 673 if token.previous and token.previous.type == Type.PARAMETERS: |
654 self._HandleError(errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS, | 674 self._HandleError( |
| 675 errors.INTERFACE_CONSTRUCTOR_CANNOT_HAVE_PARAMS, |
655 'Interface constructor cannot have parameters', | 676 'Interface constructor cannot have parameters', |
656 token.previous) | 677 token.previous) |
657 elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see') | 678 elif (state.InTopLevel() and jsdoc and not jsdoc.HasFlag('see') |
658 and not jsdoc.InheritsDocumentation() | 679 and not jsdoc.InheritsDocumentation() |
659 and not state.InObjectLiteralDescendant() and not | 680 and not state.InObjectLiteralDescendant() and not |
660 jsdoc.IsInvalidated()): | 681 jsdoc.IsInvalidated()): |
661 distance, edit = jsdoc.CompareParameters(state.GetParams()) | 682 distance, edit = jsdoc.CompareParameters(state.GetParams()) |
662 if distance: | 683 if distance: |
663 params_iter = iter(state.GetParams()) | 684 params_iter = iter(state.GetParams()) |
664 docs_iter = iter(jsdoc.ordered_params) | 685 docs_iter = iter(jsdoc.ordered_params) |
665 | 686 |
666 for op in edit: | 687 for op in edit: |
667 if op == 'I': | 688 if op == 'I': |
668 # Insertion. | 689 # Insertion. |
669 # Parsing doc comments is the same for all languages | 690 # Parsing doc comments is the same for all languages |
670 # but some languages care about parameters that don't have | 691 # but some languages care about parameters that don't have |
671 # doc comments and some languages don't care. | 692 # doc comments and some languages don't care. |
672 # Languages that don't allow variables to by typed such as | 693 # Languages that don't allow variables to by typed such as |
673 # JavaScript care but languages such as ActionScript or Java | 694 # JavaScript care but languages such as ActionScript or Java |
674 # that allow variables to be typed don't care. | 695 # that allow variables to be typed don't care. |
675 if not self._limited_doc_checks: | 696 if not self._limited_doc_checks: |
676 self.HandleMissingParameterDoc(token, params_iter.next()) | 697 self.HandleMissingParameterDoc(token, params_iter.next()) |
677 | 698 |
678 elif op == 'D': | 699 elif op == 'D': |
679 # Deletion | 700 # Deletion |
680 self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION, | 701 self._HandleError(errors.EXTRA_PARAMETER_DOCUMENTATION, |
681 'Found docs for non-existing parameter: "%s"' % | 702 'Found docs for non-existing parameter: "%s"' % |
682 docs_iter.next(), token) | 703 docs_iter.next(), token) |
683 elif op == 'S': | 704 elif op == 'S': |
684 # Substitution | 705 # Substitution |
685 if not self._limited_doc_checks: | 706 if not self._limited_doc_checks: |
686 self._HandleError(errors.WRONG_PARAMETER_DOCUMENTATION, | 707 self._HandleError( |
| 708 errors.WRONG_PARAMETER_DOCUMENTATION, |
687 'Parameter mismatch: got "%s", expected "%s"' % | 709 'Parameter mismatch: got "%s", expected "%s"' % |
688 (params_iter.next(), docs_iter.next()), token) | 710 (params_iter.next(), docs_iter.next()), token) |
689 | 711 |
690 else: | 712 else: |
691 # Equality - just advance the iterators | 713 # Equality - just advance the iterators |
692 params_iter.next() | 714 params_iter.next() |
693 docs_iter.next() | 715 docs_iter.next() |
694 | 716 |
695 elif type == Type.STRING_TEXT: | 717 elif token_type == Type.STRING_TEXT: |
696 # If this is the first token after the start of the string, but it's at | 718 # If this is the first token after the start of the string, but it's at |
697 # the end of a line, we know we have a multi-line string. | 719 # the end of a line, we know we have a multi-line string. |
698 if token.previous.type in (Type.SINGLE_QUOTE_STRING_START, | 720 if token.previous.type in ( |
| 721 Type.SINGLE_QUOTE_STRING_START, |
699 Type.DOUBLE_QUOTE_STRING_START) and last_in_line: | 722 Type.DOUBLE_QUOTE_STRING_START) and last_in_line: |
700 self._HandleError(errors.MULTI_LINE_STRING, | 723 self._HandleError(errors.MULTI_LINE_STRING, |
701 'Multi-line strings are not allowed', token) | 724 'Multi-line strings are not allowed', token) |
702 | |
703 | 725 |
704 # This check is orthogonal to the ones above, and repeats some types, so | 726 # This check is orthogonal to the ones above, and repeats some types, so |
705 # it is a plain if and not an elif. | 727 # it is a plain if and not an elif. |
706 if token.type in Type.COMMENT_TYPES: | 728 if token.type in Type.COMMENT_TYPES: |
707 if self.ILLEGAL_TAB.search(token.string): | 729 if self.ILLEGAL_TAB.search(token.string): |
708 self._HandleError(errors.ILLEGAL_TAB, | 730 self._HandleError(errors.ILLEGAL_TAB, |
709 'Illegal tab in comment "%s"' % token.string, token) | 731 'Illegal tab in comment "%s"' % token.string, token) |
710 | 732 |
711 trimmed = token.string.rstrip() | 733 trimmed = token.string.rstrip() |
712 if last_in_line and token.string != trimmed: | 734 if last_in_line and token.string != trimmed: |
713 # Check for extra whitespace at the end of a line. | 735 # Check for extra whitespace at the end of a line. |
714 self._HandleError(errors.EXTRA_SPACE, 'Extra space at end of line', | 736 self._HandleError( |
715 token, Position(len(trimmed), len(token.string) - len(trimmed))) | 737 errors.EXTRA_SPACE, 'Extra space at end of line', token, |
| 738 position=Position(len(trimmed), len(token.string) - len(trimmed))) |
716 | 739 |
717 # This check is also orthogonal since it is based on metadata. | 740 # This check is also orthogonal since it is based on metadata. |
718 if token.metadata.is_implied_semicolon: | 741 if token.metadata.is_implied_semicolon: |
719 self._HandleError(errors.MISSING_SEMICOLON, | 742 self._HandleError(errors.MISSING_SEMICOLON, |
720 'Missing semicolon at end of line', token) | 743 'Missing semicolon at end of line', token) |
721 | 744 |
722 def _HandleStartBracket(self, token, last_non_space_token): | 745 def _HandleStartBracket(self, token, last_non_space_token): |
723 """Handles a token that is an open bracket. | 746 """Handles a token that is an open bracket. |
724 | 747 |
725 Args: | 748 Args: |
726 token: The token to handle. | 749 token: The token to handle. |
727 last_non_space_token: The last token that was not a space. | 750 last_non_space_token: The last token that was not a space. |
728 """ | 751 """ |
729 if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and | 752 if (not token.IsFirstInLine() and token.previous.type == Type.WHITESPACE and |
730 last_non_space_token and | 753 last_non_space_token and |
731 last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES): | 754 last_non_space_token.type in Type.EXPRESSION_ENDER_TYPES): |
732 self._HandleError(errors.EXTRA_SPACE, 'Extra space before "["', | 755 self._HandleError( |
733 token.previous, Position.All(token.previous.string)) | 756 errors.EXTRA_SPACE, 'Extra space before "["', |
| 757 token.previous, position=Position.All(token.previous.string)) |
734 # If the [ token is the first token in a line we shouldn't complain | 758 # If the [ token is the first token in a line we shouldn't complain |
735 # about a missing space before [. This is because some Ecma script | 759 # about a missing space before [. This is because some Ecma script |
736 # languages allow syntax like: | 760 # languages allow syntax like: |
737 # [Annotation] | 761 # [Annotation] |
738 # class MyClass {...} | 762 # class MyClass {...} |
739 # So we don't want to blindly warn about missing spaces before [. | 763 # So we don't want to blindly warn about missing spaces before [. |
740 # In the the future, when rules for computing exactly how many spaces | 764 # In the the future, when rules for computing exactly how many spaces |
741 # lines should be indented are added, then we can return errors for | 765 # lines should be indented are added, then we can return errors for |
742 # [ tokens that are improperly indented. | 766 # [ tokens that are improperly indented. |
743 # For example: | 767 # For example: |
744 # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName = | 768 # var someVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongVariableName = |
745 # [a,b,c]; | 769 # [a,b,c]; |
746 # should trigger a proper indentation warning message as [ is not indented | 770 # should trigger a proper indentation warning message as [ is not indented |
747 # by four spaces. | 771 # by four spaces. |
748 elif (not token.IsFirstInLine() and token.previous and | 772 elif (not token.IsFirstInLine() and token.previous and |
749 not token.previous.type in ( | 773 token.previous.type not in ( |
750 [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] + | 774 [Type.WHITESPACE, Type.START_PAREN, Type.START_BRACKET] + |
751 Type.EXPRESSION_ENDER_TYPES)): | 775 Type.EXPRESSION_ENDER_TYPES)): |
752 self._HandleError(errors.MISSING_SPACE, 'Missing space before "["', | 776 self._HandleError(errors.MISSING_SPACE, 'Missing space before "["', |
753 token, Position.AtBeginning()) | 777 token, position=Position.AtBeginning()) |
754 | 778 |
755 def Finalize(self, state, tokenizer_mode): | 779 def Finalize(self, state): |
| 780 """Perform all checks that need to occur after all lines are processed. |
| 781 |
| 782 Args: |
| 783 state: State of the parser after parsing all tokens |
| 784 |
| 785 Raises: |
| 786 TypeError: If not overridden. |
| 787 """ |
756 last_non_space_token = state.GetLastNonSpaceToken() | 788 last_non_space_token = state.GetLastNonSpaceToken() |
757 # Check last line for ending with newline. | 789 # Check last line for ending with newline. |
758 if state.GetLastLine() and not (state.GetLastLine().isspace() or | 790 if state.GetLastLine() and not ( |
| 791 state.GetLastLine().isspace() or |
759 state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()): | 792 state.GetLastLine().rstrip('\n\r\f') != state.GetLastLine()): |
760 self._HandleError( | 793 self._HandleError( |
761 errors.FILE_MISSING_NEWLINE, | 794 errors.FILE_MISSING_NEWLINE, |
762 'File does not end with new line. (%s)' % state.GetLastLine(), | 795 'File does not end with new line. (%s)' % state.GetLastLine(), |
763 last_non_space_token) | 796 last_non_space_token) |
764 | 797 |
765 # Check that the mode is not mid comment, argument list, etc. | |
766 if not tokenizer_mode == Modes.TEXT_MODE: | |
767 self._HandleError( | |
768 errors.FILE_IN_BLOCK, | |
769 'File ended in mode "%s".' % tokenizer_mode, | |
770 last_non_space_token) | |
771 | |
772 try: | 798 try: |
773 self._indentation.Finalize() | 799 self._indentation.Finalize() |
774 except Exception, e: | 800 except Exception, e: |
775 self._HandleError( | 801 self._HandleError( |
776 errors.FILE_DOES_NOT_PARSE, | 802 errors.FILE_DOES_NOT_PARSE, |
777 str(e), | 803 str(e), |
778 last_non_space_token) | 804 last_non_space_token) |
779 | 805 |
780 def GetLongLineExceptions(self): | 806 def GetLongLineExceptions(self): |
781 """Gets a list of regexps for lines which can be longer than the limit.""" | 807 """Gets a list of regexps for lines which can be longer than the limit. |
| 808 |
| 809 Returns: |
| 810 A list of regexps, used as matches (rather than searches). |
| 811 """ |
782 return [] | 812 return [] |
783 | 813 |
784 def InExplicitlyTypedLanguage(self): | 814 def InExplicitlyTypedLanguage(self): |
785 """Returns whether this ecma implementation is explicitly typed.""" | 815 """Returns whether this ecma implementation is explicitly typed.""" |
786 return False | 816 return False |
OLD | NEW |