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 # |
(...skipping 23 matching lines...) Expand all Loading... |
34 from closure_linter import javascripttokenizer | 34 from closure_linter import javascripttokenizer |
35 from closure_linter import javascripttokens | 35 from closure_linter import javascripttokens |
36 from closure_linter import statetracker | 36 from closure_linter import statetracker |
37 from closure_linter import tokenutil | 37 from closure_linter import tokenutil |
38 from closure_linter.common import error | 38 from closure_linter.common import error |
39 from closure_linter.common import position | 39 from closure_linter.common import position |
40 | 40 |
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 # TODO(user): When flipping this to True, remove logic from unit tests |
| 45 # that overrides this flag. |
| 46 flags.DEFINE_boolean('dot_on_next_line', False, 'Require dots to be' |
| 47 'placed on the next line for wrapped expressions') |
| 48 |
| 49 flags.DEFINE_boolean('check_trailing_comma', False, 'Check trailing commas' |
| 50 ' (ES3, not needed from ES5 onwards)') |
44 | 51 |
45 # TODO(robbyw): Check for extra parens on return statements | 52 # TODO(robbyw): Check for extra parens on return statements |
46 # TODO(robbyw): Check for 0px in strings | 53 # TODO(robbyw): Check for 0px in strings |
47 # TODO(robbyw): Ensure inline jsDoc is in {} | 54 # TODO(robbyw): Ensure inline jsDoc is in {} |
48 # TODO(robbyw): Check for valid JS types in parameter docs | 55 # TODO(robbyw): Check for valid JS types in parameter docs |
49 | 56 |
50 # Shorthand | 57 # Shorthand |
51 Context = ecmametadatapass.EcmaContext | 58 Context = ecmametadatapass.EcmaContext |
52 Error = error.Error | 59 Error = error.Error |
53 Modes = javascripttokenizer.JavaScriptModes | 60 Modes = javascripttokenizer.JavaScriptModes |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
85 | 92 |
86 # Regex for form of author lines after the @author tag. | 93 # Regex for form of author lines after the @author tag. |
87 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') | 94 AUTHOR_SPEC = re.compile(r'(\s*)[^\s]+@[^(\s]+(\s*)\(.+\)') |
88 | 95 |
89 # Acceptable tokens to remove for line too long testing. | 96 # Acceptable tokens to remove for line too long testing. |
90 LONG_LINE_IGNORE = frozenset( | 97 LONG_LINE_IGNORE = frozenset( |
91 ['*', '//', '@see'] + | 98 ['*', '//', '@see'] + |
92 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) | 99 ['@%s' % tag for tag in statetracker.DocFlag.HAS_TYPE]) |
93 | 100 |
94 JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([ | 101 JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED = frozenset([ |
95 '@param', '@return', '@returns']) | 102 '@fileoverview', '@param', '@return', '@returns']) |
96 | 103 |
97 def __init__(self): | 104 def __init__(self): |
98 """Initialize this lint rule object.""" | 105 """Initialize this lint rule object.""" |
99 checkerbase.LintRulesBase.__init__(self) | 106 checkerbase.LintRulesBase.__init__(self) |
100 if EcmaScriptLintRules.max_line_length == -1: | 107 if EcmaScriptLintRules.max_line_length == -1: |
101 EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength() | 108 EcmaScriptLintRules.max_line_length = errorrules.GetMaxLineLength() |
102 | 109 |
103 def Initialize(self, checker, limited_doc_checks, is_html): | 110 def Initialize(self, checker, limited_doc_checks, is_html): |
104 """Initialize this lint rule object before parsing a new file.""" | 111 """Initialize this lint rule object before parsing a new file.""" |
105 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks, | 112 checkerbase.LintRulesBase.Initialize(self, checker, limited_doc_checks, |
(...skipping 15 matching lines...) Expand all Loading... |
121 # and DOC_FLAG tokens. | 128 # and DOC_FLAG tokens. |
122 line_number = last_token.line_number | 129 line_number = last_token.line_number |
123 token = last_token | 130 token = last_token |
124 | 131 |
125 # Build a representation of the string where spaces indicate potential | 132 # Build a representation of the string where spaces indicate potential |
126 # line-break locations. | 133 # line-break locations. |
127 line = [] | 134 line = [] |
128 while token and token.line_number == line_number: | 135 while token and token.line_number == line_number: |
129 if state.IsTypeToken(token): | 136 if state.IsTypeToken(token): |
130 line.insert(0, 'x' * len(token.string)) | 137 line.insert(0, 'x' * len(token.string)) |
131 elif token.type in (Type.IDENTIFIER, Type.NORMAL): | 138 elif token.type in (Type.IDENTIFIER, Type.OPERATOR): |
132 # Dots are acceptable places to wrap. | 139 # Dots are acceptable places to wrap (may be tokenized as identifiers). |
133 line.insert(0, token.string.replace('.', ' ')) | 140 line.insert(0, token.string.replace('.', ' ')) |
134 else: | 141 else: |
135 line.insert(0, token.string) | 142 line.insert(0, token.string) |
136 token = token.previous | 143 token = token.previous |
137 | 144 |
138 line = ''.join(line) | 145 line = ''.join(line) |
139 line = line.rstrip('\n\r\f') | 146 line = line.rstrip('\n\r\f') |
140 try: | 147 try: |
141 length = len(unicode(line, 'utf-8')) | 148 length = len(unicode(line, 'utf-8')) |
142 except (LookupError, UnicodeDecodeError): | 149 except (LookupError, UnicodeDecodeError): |
(...skipping 22 matching lines...) Expand all Loading... |
165 | 172 |
166 # Custom tags like @requires may have url like descriptions, so ignore | 173 # Custom tags like @requires may have url like descriptions, so ignore |
167 # the tag, similar to how we handle @see. | 174 # the tag, similar to how we handle @see. |
168 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags]) | 175 custom_tags = set(['@%s' % f for f in FLAGS.custom_jsdoc_tags]) |
169 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) | 176 if (len(parts.difference(self.LONG_LINE_IGNORE | custom_tags)) |
170 > max_parts): | 177 > max_parts): |
171 self._HandleError( | 178 self._HandleError( |
172 errors.LINE_TOO_LONG, | 179 errors.LINE_TOO_LONG, |
173 'Line too long (%d characters).' % len(line), last_token) | 180 'Line too long (%d characters).' % len(line), last_token) |
174 | 181 |
175 def _CheckJsDocType(self, token): | 182 def _CheckJsDocType(self, token, js_type): |
176 """Checks the given type for style errors. | 183 """Checks the given type for style errors. |
177 | 184 |
178 Args: | 185 Args: |
179 token: The DOC_FLAG token for the flag whose type to check. | 186 token: The DOC_FLAG token for the flag whose type to check. |
| 187 js_type: The flag's typeannotation.TypeAnnotation instance. |
180 """ | 188 """ |
181 flag = token.attached_object | 189 if not js_type: return |
182 flag_type = flag.type | |
183 if flag_type and flag_type is not None and not flag_type.isspace(): | |
184 pieces = self.TYPE_SPLIT.split(flag_type) | |
185 if len(pieces) == 1 and flag_type.count('|') == 1 and ( | |
186 flag_type.endswith('|null') or flag_type.startswith('null|')): | |
187 self._HandleError( | |
188 errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, | |
189 'Prefer "?Type" to "Type|null": "%s"' % flag_type, token) | |
190 | 190 |
191 # TODO(user): We should do actual parsing of JsDoc types to report an | 191 if js_type.type_group and len(js_type.sub_types) == 2: |
192 # error for wrong usage of '?' and '|' e.g. {?number|string|null} etc. | 192 identifiers = [t.identifier for t in js_type.sub_types] |
| 193 if 'null' in identifiers: |
| 194 # Don't warn if the identifier is a template type (e.g. {TYPE|null}. |
| 195 if not identifiers[0].isupper() and not identifiers[1].isupper(): |
| 196 self._HandleError( |
| 197 errors.JSDOC_PREFER_QUESTION_TO_PIPE_NULL, |
| 198 'Prefer "?Type" to "Type|null": "%s"' % js_type, token) |
193 | 199 |
194 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( | 200 # TODO(user): We should report an error for wrong usage of '?' and '|' |
195 flag.type_start_token.type != Type.DOC_START_BRACE or | 201 # e.g. {?number|string|null} etc. |
196 flag.type_end_token.type != Type.DOC_END_BRACE): | 202 |
197 self._HandleError( | 203 for sub_type in js_type.IterTypes(): |
198 errors.MISSING_BRACES_AROUND_TYPE, | 204 self._CheckJsDocType(token, sub_type) |
199 'Type must always be surrounded by curly braces.', token) | |
200 | 205 |
201 def _CheckForMissingSpaceBeforeToken(self, token): | 206 def _CheckForMissingSpaceBeforeToken(self, token): |
202 """Checks for a missing space at the beginning of a token. | 207 """Checks for a missing space at the beginning of a token. |
203 | 208 |
204 Reports a MISSING_SPACE error if the token does not begin with a space or | 209 Reports a MISSING_SPACE error if the token does not begin with a space or |
205 the previous token doesn't end with a space and the previous token is on the | 210 the previous token doesn't end with a space and the previous token is on the |
206 same line as the token. | 211 same line as the token. |
207 | 212 |
208 Args: | 213 Args: |
209 token: The token being checked | 214 token: The token being checked |
(...skipping 11 matching lines...) Expand all Loading... |
221 def _CheckOperator(self, token): | 226 def _CheckOperator(self, token): |
222 """Checks an operator for spacing and line style. | 227 """Checks an operator for spacing and line style. |
223 | 228 |
224 Args: | 229 Args: |
225 token: The operator token. | 230 token: The operator token. |
226 """ | 231 """ |
227 last_code = token.metadata.last_code | 232 last_code = token.metadata.last_code |
228 | 233 |
229 if not self._ExpectSpaceBeforeOperator(token): | 234 if not self._ExpectSpaceBeforeOperator(token): |
230 if (token.previous and token.previous.type == Type.WHITESPACE and | 235 if (token.previous and token.previous.type == Type.WHITESPACE and |
231 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER)): | 236 last_code and last_code.type in (Type.NORMAL, Type.IDENTIFIER) and |
| 237 last_code.line_number == token.line_number): |
232 self._HandleError( | 238 self._HandleError( |
233 errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, | 239 errors.EXTRA_SPACE, 'Extra space before "%s"' % token.string, |
234 token.previous, position=Position.All(token.previous.string)) | 240 token.previous, position=Position.All(token.previous.string)) |
235 | 241 |
236 elif (token.previous and | 242 elif (token.previous and |
237 not token.previous.IsComment() and | 243 not token.previous.IsComment() and |
| 244 not tokenutil.IsDot(token) and |
238 token.previous.type in Type.EXPRESSION_ENDER_TYPES): | 245 token.previous.type in Type.EXPRESSION_ENDER_TYPES): |
239 self._HandleError(errors.MISSING_SPACE, | 246 self._HandleError(errors.MISSING_SPACE, |
240 'Missing space before "%s"' % token.string, token, | 247 'Missing space before "%s"' % token.string, token, |
241 position=Position.AtBeginning()) | 248 position=Position.AtBeginning()) |
242 | 249 |
243 # Check that binary operators are not used to start lines. | 250 # Check wrapping of operators. |
244 if ((not last_code or last_code.line_number != token.line_number) and | 251 next_code = tokenutil.GetNextCodeToken(token) |
| 252 |
| 253 is_dot = tokenutil.IsDot(token) |
| 254 wrapped_before = last_code and last_code.line_number != token.line_number |
| 255 wrapped_after = next_code and next_code.line_number != token.line_number |
| 256 |
| 257 if FLAGS.dot_on_next_line and is_dot and wrapped_after: |
| 258 self._HandleError( |
| 259 errors.LINE_ENDS_WITH_DOT, |
| 260 '"." must go on the following line', |
| 261 token) |
| 262 if (not is_dot and wrapped_before and |
245 not token.metadata.IsUnaryOperator()): | 263 not token.metadata.IsUnaryOperator()): |
246 self._HandleError( | 264 self._HandleError( |
247 errors.LINE_STARTS_WITH_OPERATOR, | 265 errors.LINE_STARTS_WITH_OPERATOR, |
248 'Binary operator should go on previous line "%s"' % token.string, | 266 'Binary operator must go on previous line "%s"' % token.string, |
249 token) | 267 token) |
250 | 268 |
| 269 def _IsLabel(self, token): |
| 270 # A ':' token is considered part of a label if it occurs in a case |
| 271 # statement, a plain label, or an object literal, i.e. is not part of a |
| 272 # ternary. |
| 273 |
| 274 return (token.string == ':' and |
| 275 token.metadata.context.type in (Context.LITERAL_ELEMENT, |
| 276 Context.CASE_BLOCK, |
| 277 Context.STATEMENT)) |
| 278 |
251 def _ExpectSpaceBeforeOperator(self, token): | 279 def _ExpectSpaceBeforeOperator(self, token): |
252 """Returns whether a space should appear before the given operator token. | 280 """Returns whether a space should appear before the given operator token. |
253 | 281 |
254 Args: | 282 Args: |
255 token: The operator token. | 283 token: The operator token. |
256 | 284 |
257 Returns: | 285 Returns: |
258 Whether there should be a space before the token. | 286 Whether there should be a space before the token. |
259 """ | 287 """ |
260 if token.string == ',' or token.metadata.IsUnaryPostOperator(): | 288 if token.string == ',' or token.metadata.IsUnaryPostOperator(): |
261 return False | 289 return False |
262 | 290 |
| 291 if tokenutil.IsDot(token): |
| 292 return False |
| 293 |
263 # Colons should appear in labels, object literals, the case of a switch | 294 # Colons should appear in labels, object literals, the case of a switch |
264 # statement, and ternary operator. Only want a space in the case of the | 295 # statement, and ternary operator. Only want a space in the case of the |
265 # ternary operator. | 296 # ternary operator. |
266 if (token.string == ':' and | 297 if self._IsLabel(token): |
267 token.metadata.context.type in (Context.LITERAL_ELEMENT, | |
268 Context.CASE_BLOCK, | |
269 Context.STATEMENT)): | |
270 return False | 298 return False |
271 | 299 |
272 if token.metadata.IsUnaryOperator() and token.IsFirstInLine(): | 300 if token.metadata.IsUnaryOperator() and token.IsFirstInLine(): |
273 return False | 301 return False |
274 | 302 |
275 return True | 303 return True |
276 | 304 |
277 def CheckToken(self, token, state): | 305 def CheckToken(self, token, state): |
278 """Checks a token, given the current parser_state, for warnings and errors. | 306 """Checks a token, given the current parser_state, for warnings and errors. |
279 | 307 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
311 space_count = len(token.string) - len(token.string.lstrip()) | 339 space_count = len(token.string) - len(token.string.lstrip()) |
312 if space_count: | 340 if space_count: |
313 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', | 341 self._HandleError(errors.EXTRA_SPACE, 'Extra space after "("', |
314 token, position=Position(0, space_count)) | 342 token, position=Position(0, space_count)) |
315 | 343 |
316 elif (token_type == Type.START_BLOCK and | 344 elif (token_type == Type.START_BLOCK and |
317 token.metadata.context.type == Context.BLOCK): | 345 token.metadata.context.type == Context.BLOCK): |
318 self._CheckForMissingSpaceBeforeToken(token) | 346 self._CheckForMissingSpaceBeforeToken(token) |
319 | 347 |
320 elif token_type == Type.END_BLOCK: | 348 elif token_type == Type.END_BLOCK: |
321 # This check is for object literal end block tokens, but there is no need | |
322 # to test that condition since a comma at the end of any other kind of | |
323 # block is undoubtedly a parse error. | |
324 last_code = token.metadata.last_code | 349 last_code = token.metadata.last_code |
325 if last_code.IsOperator(','): | 350 |
326 self._HandleError( | 351 if FLAGS.check_trailing_comma: |
327 errors.COMMA_AT_END_OF_LITERAL, | 352 if last_code.IsOperator(','): |
328 'Illegal comma at end of object literal', last_code, | 353 self._HandleError( |
329 position=Position.All(last_code.string)) | 354 errors.COMMA_AT_END_OF_LITERAL, |
| 355 'Illegal comma at end of object literal', last_code, |
| 356 position=Position.All(last_code.string)) |
330 | 357 |
331 if state.InFunction() and state.IsFunctionClose(): | 358 if state.InFunction() and state.IsFunctionClose(): |
332 is_immediately_called = (token.next and | |
333 token.next.type == Type.START_PAREN) | |
334 if state.InTopLevelFunction(): | 359 if state.InTopLevelFunction(): |
335 # A semicolons should not be included at the end of a function | 360 # A semicolons should not be included at the end of a function |
336 # declaration. | 361 # declaration. |
337 if not state.InAssignedFunction(): | 362 if not state.InAssignedFunction(): |
338 if not last_in_line and token.next.type == Type.SEMICOLON: | 363 if not last_in_line and token.next.type == Type.SEMICOLON: |
339 self._HandleError( | 364 self._HandleError( |
340 errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, | 365 errors.ILLEGAL_SEMICOLON_AFTER_FUNCTION, |
341 'Illegal semicolon after function declaration', | 366 'Illegal semicolon after function declaration', |
342 token.next, position=Position.All(token.next.string)) | 367 token.next, position=Position.All(token.next.string)) |
343 | 368 |
344 # A semicolon should be included at the end of a function expression | 369 # A semicolon should be included at the end of a function expression |
345 # that is not immediately called. | 370 # that is not immediately called or used by a dot operator. |
346 if state.InAssignedFunction(): | 371 if (state.InAssignedFunction() and token.next |
347 if not is_immediately_called and ( | 372 and token.next.type != Type.SEMICOLON): |
348 last_in_line or token.next.type != Type.SEMICOLON): | 373 next_token = tokenutil.GetNextCodeToken(token) |
| 374 is_immediately_used = next_token and ( |
| 375 next_token.type == Type.START_PAREN or |
| 376 tokenutil.IsDot(next_token)) |
| 377 if not is_immediately_used: |
349 self._HandleError( | 378 self._HandleError( |
350 errors.MISSING_SEMICOLON_AFTER_FUNCTION, | 379 errors.MISSING_SEMICOLON_AFTER_FUNCTION, |
351 'Missing semicolon after function assigned to a variable', | 380 'Missing semicolon after function assigned to a variable', |
352 token, position=Position.AtEnd(token.string)) | 381 token, position=Position.AtEnd(token.string)) |
353 | 382 |
354 if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK: | 383 if state.InInterfaceMethod() and last_code.type != Type.START_BLOCK: |
355 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, | 384 self._HandleError(errors.INTERFACE_METHOD_CANNOT_HAVE_CODE, |
356 'Interface methods cannot contain code', last_code) | 385 'Interface methods cannot contain code', last_code) |
357 | 386 |
358 elif (state.IsBlockClose() and | 387 elif (state.IsBlockClose() and |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
395 lambda token: token.type == Type.KEYWORD and token.string == 'for', | 424 lambda token: token.type == Type.KEYWORD and token.string == 'for', |
396 end_func=lambda token: token.type == Type.SEMICOLON, | 425 end_func=lambda token: token.type == Type.SEMICOLON, |
397 distance=None, | 426 distance=None, |
398 reverse=True) | 427 reverse=True) |
399 | 428 |
400 if not for_token: | 429 if not for_token: |
401 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', | 430 self._HandleError(errors.REDUNDANT_SEMICOLON, 'Redundant semicolon', |
402 token, position=Position.All(token.string)) | 431 token, position=Position.All(token.string)) |
403 | 432 |
404 elif token_type == Type.START_PAREN: | 433 elif token_type == Type.START_PAREN: |
405 if token.previous and token.previous.type == Type.KEYWORD: | 434 # Ensure that opening parentheses have a space before any keyword |
| 435 # that is not being invoked like a member function. |
| 436 if (token.previous and token.previous.type == Type.KEYWORD and |
| 437 (not token.previous.metadata or |
| 438 not token.previous.metadata.last_code or |
| 439 not token.previous.metadata.last_code.string or |
| 440 token.previous.metadata.last_code.string[-1:] != '.')): |
406 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', | 441 self._HandleError(errors.MISSING_SPACE, 'Missing space before "("', |
407 token, position=Position.AtBeginning()) | 442 token, position=Position.AtBeginning()) |
408 elif token.previous and token.previous.type == Type.WHITESPACE: | 443 elif token.previous and token.previous.type == Type.WHITESPACE: |
409 before_space = token.previous.previous | 444 before_space = token.previous.previous |
| 445 # Ensure that there is no extra space before a function invocation, |
| 446 # even if the function being invoked happens to be a keyword. |
410 if (before_space and before_space.line_number == token.line_number and | 447 if (before_space and before_space.line_number == token.line_number and |
411 before_space.type == Type.IDENTIFIER): | 448 before_space.type == Type.IDENTIFIER or |
| 449 (before_space.type == Type.KEYWORD and before_space.metadata and |
| 450 before_space.metadata.last_code and |
| 451 before_space.metadata.last_code.string and |
| 452 before_space.metadata.last_code.string[-1:] == '.')): |
412 self._HandleError( | 453 self._HandleError( |
413 errors.EXTRA_SPACE, 'Extra space before "("', | 454 errors.EXTRA_SPACE, 'Extra space before "("', |
414 token.previous, position=Position.All(token.previous.string)) | 455 token.previous, position=Position.All(token.previous.string)) |
415 | 456 |
416 elif token_type == Type.START_BRACKET: | 457 elif token_type == Type.START_BRACKET: |
417 self._HandleStartBracket(token, last_non_space_token) | 458 self._HandleStartBracket(token, last_non_space_token) |
418 elif token_type in (Type.END_PAREN, Type.END_BRACKET): | 459 elif token_type in (Type.END_PAREN, Type.END_BRACKET): |
419 # Ensure there is no space before closing parentheses, except when | 460 # Ensure there is no space before closing parentheses, except when |
420 # it's in a for statement with an omitted section, or when it's at the | 461 # it's in a for statement with an omitted section, or when it's at the |
421 # beginning of a line. | 462 # beginning of a line. |
| 463 |
| 464 last_code = token.metadata.last_code |
| 465 if FLAGS.check_trailing_comma and token_type == Type.END_BRACKET: |
| 466 if last_code.IsOperator(','): |
| 467 self._HandleError( |
| 468 errors.COMMA_AT_END_OF_LITERAL, |
| 469 'Illegal comma at end of array literal', last_code, |
| 470 position=Position.All(last_code.string)) |
| 471 |
422 if (token.previous and token.previous.type == Type.WHITESPACE and | 472 if (token.previous and token.previous.type == Type.WHITESPACE and |
423 not token.previous.IsFirstInLine() and | 473 not token.previous.IsFirstInLine() and |
424 not (last_non_space_token and last_non_space_token.line_number == | 474 not (last_non_space_token and last_non_space_token.line_number == |
425 token.line_number and | 475 token.line_number and |
426 last_non_space_token.type == Type.SEMICOLON)): | 476 last_non_space_token.type == Type.SEMICOLON)): |
427 self._HandleError( | 477 self._HandleError( |
428 errors.EXTRA_SPACE, 'Extra space before "%s"' % | 478 errors.EXTRA_SPACE, 'Extra space before "%s"' % |
429 token.string, token.previous, | 479 token.string, token.previous, |
430 position=Position.All(token.previous.string)) | 480 position=Position.All(token.previous.string)) |
431 | 481 |
432 if token.type == Type.END_BRACKET: | |
433 last_code = token.metadata.last_code | |
434 if last_code.IsOperator(','): | |
435 self._HandleError( | |
436 errors.COMMA_AT_END_OF_LITERAL, | |
437 'Illegal comma at end of array literal', last_code, | |
438 position=Position.All(last_code.string)) | |
439 | |
440 elif token_type == Type.WHITESPACE: | 482 elif token_type == Type.WHITESPACE: |
441 if self.ILLEGAL_TAB.search(token.string): | 483 if self.ILLEGAL_TAB.search(token.string): |
442 if token.IsFirstInLine(): | 484 if token.IsFirstInLine(): |
443 if token.next: | 485 if token.next: |
444 self._HandleError( | 486 self._HandleError( |
445 errors.ILLEGAL_TAB, | 487 errors.ILLEGAL_TAB, |
446 'Illegal tab in whitespace before "%s"' % token.next.string, | 488 'Illegal tab in whitespace before "%s"' % token.next.string, |
447 token, position=Position.All(token.string)) | 489 token, position=Position.All(token.string)) |
448 else: | 490 else: |
449 self._HandleError( | 491 self._HandleError( |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
485 | 527 |
486 elif flag.flag_type == 'suppress': | 528 elif flag.flag_type == 'suppress': |
487 if flag.type is None: | 529 if flag.type is None: |
488 # A syntactically invalid suppress tag will get tokenized as a normal | 530 # A syntactically invalid suppress tag will get tokenized as a normal |
489 # flag, indicating an error. | 531 # flag, indicating an error. |
490 self._HandleError( | 532 self._HandleError( |
491 errors.INCORRECT_SUPPRESS_SYNTAX, | 533 errors.INCORRECT_SUPPRESS_SYNTAX, |
492 'Invalid suppress syntax: should be @suppress {errortype}. ' | 534 'Invalid suppress syntax: should be @suppress {errortype}. ' |
493 'Spaces matter.', token) | 535 'Spaces matter.', token) |
494 else: | 536 else: |
495 for suppress_type in re.split(r'\||,', flag.type): | 537 for suppress_type in flag.jstype.IterIdentifiers(): |
496 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: | 538 if suppress_type not in state.GetDocFlag().SUPPRESS_TYPES: |
497 self._HandleError( | 539 self._HandleError( |
498 errors.INVALID_SUPPRESS_TYPE, | 540 errors.INVALID_SUPPRESS_TYPE, |
499 'Invalid suppression type: %s' % suppress_type, token) | 541 'Invalid suppression type: %s' % suppress_type, token) |
500 | 542 |
501 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and | 543 elif (error_check.ShouldCheck(Rule.WELL_FORMED_AUTHOR) and |
502 flag.flag_type == 'author'): | 544 flag.flag_type == 'author'): |
503 # TODO(user): In non strict mode check the author tag for as much as | 545 # TODO(user): In non strict mode check the author tag for as much as |
504 # it exists, though the full form checked below isn't required. | 546 # it exists, though the full form checked below isn't required. |
505 string = token.next.string | 547 string = token.next.string |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
544 if 'name' in token.values: | 586 if 'name' in token.values: |
545 flag_name = '@' + token.values['name'] | 587 flag_name = '@' + token.values['name'] |
546 | 588 |
547 if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED: | 589 if flag_name not in self.JSDOC_FLAGS_DESCRIPTION_NOT_REQUIRED: |
548 self._HandleError( | 590 self._HandleError( |
549 errors.MISSING_JSDOC_TAG_DESCRIPTION, | 591 errors.MISSING_JSDOC_TAG_DESCRIPTION, |
550 'Missing description in %s tag' % flag_name, token) | 592 'Missing description in %s tag' % flag_name, token) |
551 else: | 593 else: |
552 self._CheckForMissingSpaceBeforeToken(flag.description_start_token) | 594 self._CheckForMissingSpaceBeforeToken(flag.description_start_token) |
553 | 595 |
554 if flag.flag_type in state.GetDocFlag().HAS_TYPE: | 596 if flag.HasType(): |
555 if flag.type_start_token is not None: | 597 if flag.type_start_token is not None: |
556 self._CheckForMissingSpaceBeforeToken( | 598 self._CheckForMissingSpaceBeforeToken( |
557 token.attached_object.type_start_token) | 599 token.attached_object.type_start_token) |
558 | 600 |
559 if flag.type and not flag.type.isspace(): | 601 if flag.jstype and not flag.jstype.IsEmpty(): |
560 self._CheckJsDocType(token) | 602 self._CheckJsDocType(token, flag.jstype) |
| 603 |
| 604 if error_check.ShouldCheck(Rule.BRACES_AROUND_TYPE) and ( |
| 605 flag.type_start_token.type != Type.DOC_START_BRACE or |
| 606 flag.type_end_token.type != Type.DOC_END_BRACE): |
| 607 self._HandleError( |
| 608 errors.MISSING_BRACES_AROUND_TYPE, |
| 609 'Type must always be surrounded by curly braces.', token) |
561 | 610 |
562 if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): | 611 if token_type in (Type.DOC_FLAG, Type.DOC_INLINE_FLAG): |
563 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and | 612 if (token.values['name'] not in state.GetDocFlag().LEGAL_DOC and |
564 token.values['name'] not in FLAGS.custom_jsdoc_tags): | 613 token.values['name'] not in FLAGS.custom_jsdoc_tags): |
565 self._HandleError( | 614 self._HandleError( |
566 errors.INVALID_JSDOC_TAG, | 615 errors.INVALID_JSDOC_TAG, |
567 'Invalid JsDoc tag: %s' % token.values['name'], token) | 616 'Invalid JsDoc tag: %s' % token.values['name'], token) |
568 | 617 |
569 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and | 618 if (error_check.ShouldCheck(Rule.NO_BRACES_AROUND_INHERIT_DOC) and |
570 token.values['name'] == 'inheritDoc' and | 619 token.values['name'] == 'inheritDoc' and |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
630 not self.InExplicitlyTypedLanguage()): | 679 not self.InExplicitlyTypedLanguage()): |
631 # It is convention to hide public fields in some ECMA | 680 # It is convention to hide public fields in some ECMA |
632 # implementations from documentation using the @private tag. | 681 # implementations from documentation using the @private tag. |
633 self._HandleError( | 682 self._HandleError( |
634 errors.EXTRA_PRIVATE, | 683 errors.EXTRA_PRIVATE, |
635 'Member "%s" must not have @private JsDoc' % | 684 'Member "%s" must not have @private JsDoc' % |
636 identifier, token) | 685 identifier, token) |
637 | 686 |
638 # These flags are only legal on localizable message definitions; | 687 # These flags are only legal on localizable message definitions; |
639 # such variables always begin with the prefix MSG_. | 688 # such variables always begin with the prefix MSG_. |
640 for f in ('desc', 'hidden', 'meaning'): | 689 if not identifier.startswith('MSG_') and '.MSG_' not in identifier: |
641 if (jsdoc.HasFlag(f) | 690 for f in ('desc', 'hidden', 'meaning'): |
642 and not identifier.startswith('MSG_') | 691 if jsdoc.HasFlag(f): |
643 and identifier.find('.MSG_') == -1): | 692 self._HandleError( |
644 self._HandleError( | 693 errors.INVALID_USE_OF_DESC_TAG, |
645 errors.INVALID_USE_OF_DESC_TAG, | 694 'Member "%s" does not start with MSG_ and thus ' |
646 'Member "%s" should not have @%s JsDoc' % (identifier, f), | 695 'should not have @%s JsDoc' % (identifier, f), |
647 token) | 696 token) |
648 | 697 |
649 # Check for illegaly assigning live objects as prototype property values. | 698 # Check for illegaly assigning live objects as prototype property values. |
650 index = identifier.find('.prototype.') | 699 index = identifier.find('.prototype.') |
651 # Ignore anything with additional .s after the prototype. | 700 # Ignore anything with additional .s after the prototype. |
652 if index != -1 and identifier.find('.', index + 11) == -1: | 701 if index != -1 and identifier.find('.', index + 11) == -1: |
653 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) | 702 equal_operator = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES) |
654 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) | 703 next_code = tokenutil.SearchExcept(equal_operator, Type.NON_CODE_TYPES) |
655 if next_code and ( | 704 if next_code and ( |
656 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or | 705 next_code.type in (Type.START_BRACKET, Type.START_BLOCK) or |
657 next_code.IsOperator('new')): | 706 next_code.IsOperator('new')): |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
807 """Gets a list of regexps for lines which can be longer than the limit. | 856 """Gets a list of regexps for lines which can be longer than the limit. |
808 | 857 |
809 Returns: | 858 Returns: |
810 A list of regexps, used as matches (rather than searches). | 859 A list of regexps, used as matches (rather than searches). |
811 """ | 860 """ |
812 return [] | 861 return [] |
813 | 862 |
814 def InExplicitlyTypedLanguage(self): | 863 def InExplicitlyTypedLanguage(self): |
815 """Returns whether this ecma implementation is explicitly typed.""" | 864 """Returns whether this ecma implementation is explicitly typed.""" |
816 return False | 865 return False |
OLD | NEW |