| 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 |