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

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

Issue 2592193002: Remove closure_linter from Chrome (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2011 The Closure Linter Authors. All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS-IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 """Methods for checking JS files for common style guide violations.
17
18 These style guide violations should only apply to JavaScript and not an Ecma
19 scripting languages.
20 """
21
22 __author__ = ('robbyw@google.com (Robert Walker)',
23 'ajp@google.com (Andy Perelson)',
24 'jacobr@google.com (Jacob Richman)')
25
26 import re
27
28 from closure_linter import ecmalintrules
29 from closure_linter import error_check
30 from closure_linter import errors
31 from closure_linter import javascripttokenizer
32 from closure_linter import javascripttokens
33 from closure_linter import requireprovidesorter
34 from closure_linter import tokenutil
35 from closure_linter.common import error
36 from closure_linter.common import position
37
38 # Shorthand
39 Error = error.Error
40 Position = position.Position
41 Rule = error_check.Rule
42 Type = javascripttokens.JavaScriptTokenType
43
44
45 class JavaScriptLintRules(ecmalintrules.EcmaScriptLintRules):
46 """JavaScript lint rules that catch JavaScript specific style errors."""
47
48 def __init__(self, namespaces_info):
49 """Initializes a JavaScriptLintRules instance."""
50 ecmalintrules.EcmaScriptLintRules.__init__(self)
51 self._namespaces_info = namespaces_info
52 self._declared_private_member_tokens = {}
53 self._declared_private_members = set()
54 self._used_private_members = set()
55 # A stack of dictionaries, one for each function scope entered. Each
56 # dictionary is keyed by an identifier that defines a local variable and has
57 # a token as its value.
58 self._unused_local_variables_by_scope = []
59
60 def HandleMissingParameterDoc(self, token, param_name):
61 """Handle errors associated with a parameter missing a param tag."""
62 self._HandleError(errors.MISSING_PARAMETER_DOCUMENTATION,
63 'Missing docs for parameter: "%s"' % param_name, token)
64
65 # pylint: disable=too-many-statements
66 def CheckToken(self, token, state):
67 """Checks a token, given the current parser_state, for warnings and errors.
68
69 Args:
70 token: The current token under consideration
71 state: parser_state object that indicates the current state in the page
72 """
73
74 # Call the base class's CheckToken function.
75 super(JavaScriptLintRules, self).CheckToken(token, state)
76
77 # Store some convenience variables
78 namespaces_info = self._namespaces_info
79
80 if error_check.ShouldCheck(Rule.UNUSED_LOCAL_VARIABLES):
81 self._CheckUnusedLocalVariables(token, state)
82
83 if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
84 # Find all assignments to private members.
85 if token.type == Type.SIMPLE_LVALUE:
86 identifier = token.string
87 if identifier.endswith('_') and not identifier.endswith('__'):
88 doc_comment = state.GetDocComment()
89 suppressed = doc_comment and (
90 'underscore' in doc_comment.suppressions or
91 'unusedPrivateMembers' in doc_comment.suppressions)
92 if not suppressed:
93 # Look for static members defined on a provided namespace.
94 if namespaces_info:
95 namespace = namespaces_info.GetClosurizedNamespace(identifier)
96 provided_namespaces = namespaces_info.GetProvidedNamespaces()
97 else:
98 namespace = None
99 provided_namespaces = set()
100
101 # Skip cases of this.something_.somethingElse_.
102 regex = re.compile(r'^this\.[a-zA-Z_]+$')
103 if namespace in provided_namespaces or regex.match(identifier):
104 variable = identifier.split('.')[-1]
105 self._declared_private_member_tokens[variable] = token
106 self._declared_private_members.add(variable)
107 elif not identifier.endswith('__'):
108 # Consider setting public members of private members to be a usage.
109 for piece in identifier.split('.'):
110 if piece.endswith('_'):
111 self._used_private_members.add(piece)
112
113 # Find all usages of private members.
114 if token.type == Type.IDENTIFIER:
115 for piece in token.string.split('.'):
116 if piece.endswith('_'):
117 self._used_private_members.add(piece)
118
119 if token.type == Type.DOC_FLAG:
120 flag = token.attached_object
121
122 if flag.flag_type == 'param' and flag.name_token is not None:
123 self._CheckForMissingSpaceBeforeToken(
124 token.attached_object.name_token)
125
126 if flag.type is not None and flag.name is not None:
127 if error_check.ShouldCheck(Rule.VARIABLE_ARG_MARKER):
128 # Check for variable arguments marker in type.
129 if flag.jstype.IsVarArgsType() and flag.name != 'var_args':
130 self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_NAME,
131 'Variable length argument %s must be renamed '
132 'to var_args.' % flag.name,
133 token)
134 elif not flag.jstype.IsVarArgsType() and flag.name == 'var_args':
135 self._HandleError(errors.JSDOC_MISSING_VAR_ARGS_TYPE,
136 'Variable length argument %s type must start '
137 'with \'...\'.' % flag.name,
138 token)
139
140 if error_check.ShouldCheck(Rule.OPTIONAL_TYPE_MARKER):
141 # Check for optional marker in type.
142 if (flag.jstype.opt_arg and
143 not flag.name.startswith('opt_')):
144 self._HandleError(errors.JSDOC_MISSING_OPTIONAL_PREFIX,
145 'Optional parameter name %s must be prefixed '
146 'with opt_.' % flag.name,
147 token)
148 elif (not flag.jstype.opt_arg and
149 flag.name.startswith('opt_')):
150 self._HandleError(errors.JSDOC_MISSING_OPTIONAL_TYPE,
151 'Optional parameter %s type must end with =.' %
152 flag.name,
153 token)
154
155 if flag.flag_type in state.GetDocFlag().HAS_TYPE:
156 # Check for both missing type token and empty type braces '{}'
157 # Missing suppress types are reported separately and we allow enums,
158 # const, private, public and protected without types.
159 if (flag.flag_type not in state.GetDocFlag().CAN_OMIT_TYPE
160 and (not flag.jstype or flag.jstype.IsEmpty())):
161 self._HandleError(errors.MISSING_JSDOC_TAG_TYPE,
162 'Missing type in %s tag' % token.string, token)
163
164 elif flag.name_token and flag.type_end_token and tokenutil.Compare(
165 flag.type_end_token, flag.name_token) > 0:
166 self._HandleError(
167 errors.OUT_OF_ORDER_JSDOC_TAG_TYPE,
168 'Type should be immediately after %s tag' % token.string,
169 token)
170
171 elif token.type == Type.DOUBLE_QUOTE_STRING_START:
172 next_token = token.next
173 while next_token.type == Type.STRING_TEXT:
174 if javascripttokenizer.JavaScriptTokenizer.SINGLE_QUOTE.search(
175 next_token.string):
176 break
177 next_token = next_token.next
178 else:
179 self._HandleError(
180 errors.UNNECESSARY_DOUBLE_QUOTED_STRING,
181 'Single-quoted string preferred over double-quoted string.',
182 token,
183 position=Position.All(token.string))
184
185 elif token.type == Type.END_DOC_COMMENT:
186 doc_comment = state.GetDocComment()
187
188 # When @externs appears in a @fileoverview comment, it should trigger
189 # the same limited doc checks as a special filename like externs.js.
190 if doc_comment.HasFlag('fileoverview') and doc_comment.HasFlag('externs'):
191 self._SetLimitedDocChecks(True)
192
193 if (error_check.ShouldCheck(Rule.BLANK_LINES_AT_TOP_LEVEL) and
194 not self._is_html and
195 state.InTopLevel() and
196 not state.InNonScopeBlock()):
197
198 # Check if we're in a fileoverview or constructor JsDoc.
199 is_constructor = (
200 doc_comment.HasFlag('constructor') or
201 doc_comment.HasFlag('interface'))
202 # @fileoverview is an optional tag so if the dosctring is the first
203 # token in the file treat it as a file level docstring.
204 is_file_level_comment = (
205 doc_comment.HasFlag('fileoverview') or
206 not doc_comment.start_token.previous)
207
208 # If the comment is not a file overview, and it does not immediately
209 # precede some code, skip it.
210 # NOTE: The tokenutil methods are not used here because of their
211 # behavior at the top of a file.
212 next_token = token.next
213 if (not next_token or
214 (not is_file_level_comment and
215 next_token.type in Type.NON_CODE_TYPES)):
216 return
217
218 # Don't require extra blank lines around suppression of extra
219 # goog.require errors.
220 if (doc_comment.SuppressionOnly() and
221 next_token.type == Type.IDENTIFIER and
222 next_token.string in ['goog.provide', 'goog.require']):
223 return
224
225 # Find the start of this block (include comments above the block, unless
226 # this is a file overview).
227 block_start = doc_comment.start_token
228 if not is_file_level_comment:
229 token = block_start.previous
230 while token and token.type in Type.COMMENT_TYPES:
231 block_start = token
232 token = token.previous
233
234 # Count the number of blank lines before this block.
235 blank_lines = 0
236 token = block_start.previous
237 while token and token.type in [Type.WHITESPACE, Type.BLANK_LINE]:
238 if token.type == Type.BLANK_LINE:
239 # A blank line.
240 blank_lines += 1
241 elif token.type == Type.WHITESPACE and not token.line.strip():
242 # A line with only whitespace on it.
243 blank_lines += 1
244 token = token.previous
245
246 # Log errors.
247 error_message = False
248 expected_blank_lines = 0
249
250 # Only need blank line before file overview if it is not the beginning
251 # of the file, e.g. copyright is first.
252 if is_file_level_comment and blank_lines == 0 and block_start.previous:
253 error_message = 'Should have a blank line before a file overview.'
254 expected_blank_lines = 1
255 elif is_constructor and blank_lines != 3:
256 error_message = (
257 'Should have 3 blank lines before a constructor/interface.')
258 expected_blank_lines = 3
259 elif (not is_file_level_comment and not is_constructor and
260 blank_lines != 2):
261 error_message = 'Should have 2 blank lines between top-level blocks.'
262 expected_blank_lines = 2
263
264 if error_message:
265 self._HandleError(
266 errors.WRONG_BLANK_LINE_COUNT, error_message,
267 block_start, position=Position.AtBeginning(),
268 fix_data=expected_blank_lines - blank_lines)
269
270 elif token.type == Type.END_BLOCK:
271 if state.InFunction() and state.IsFunctionClose():
272 is_immediately_called = (token.next and
273 token.next.type == Type.START_PAREN)
274
275 function = state.GetFunction()
276 if not self._limited_doc_checks:
277 if (function.has_return and function.doc and
278 not is_immediately_called and
279 not function.doc.HasFlag('return') and
280 not function.doc.InheritsDocumentation() and
281 not function.doc.HasFlag('constructor')):
282 # Check for proper documentation of return value.
283 self._HandleError(
284 errors.MISSING_RETURN_DOCUMENTATION,
285 'Missing @return JsDoc in function with non-trivial return',
286 function.doc.end_token, position=Position.AtBeginning())
287 elif (not function.has_return and
288 not function.has_throw and
289 function.doc and
290 function.doc.HasFlag('return') and
291 not state.InInterfaceMethod()):
292 flag = function.doc.GetFlag('return')
293 valid_no_return_names = ['undefined', 'void', '*']
294 invalid_return = flag.jstype is None or not any(
295 sub_type.identifier in valid_no_return_names
296 for sub_type in flag.jstype.IterTypeGroup())
297
298 if invalid_return:
299 self._HandleError(
300 errors.UNNECESSARY_RETURN_DOCUMENTATION,
301 'Found @return JsDoc on function that returns nothing',
302 flag.flag_token, position=Position.AtBeginning())
303
304 # b/4073735. Method in object literal definition of prototype can
305 # safely reference 'this'.
306 prototype_object_literal = False
307 block_start = None
308 previous_code = None
309 previous_previous_code = None
310
311 # Search for cases where prototype is defined as object literal.
312 # previous_previous_code
313 # | previous_code
314 # | | block_start
315 # | | |
316 # a.b.prototype = {
317 # c : function() {
318 # this.d = 1;
319 # }
320 # }
321
322 # If in object literal, find first token of block so to find previous
323 # tokens to check above condition.
324 if state.InObjectLiteral():
325 block_start = state.GetCurrentBlockStart()
326
327 # If an object literal then get previous token (code type). For above
328 # case it should be '='.
329 if block_start:
330 previous_code = tokenutil.SearchExcept(block_start,
331 Type.NON_CODE_TYPES,
332 reverse=True)
333
334 # If previous token to block is '=' then get its previous token.
335 if previous_code and previous_code.IsOperator('='):
336 previous_previous_code = tokenutil.SearchExcept(previous_code,
337 Type.NON_CODE_TYPES,
338 reverse=True)
339
340 # If variable/token before '=' ends with '.prototype' then its above
341 # case of prototype defined with object literal.
342 prototype_object_literal = (previous_previous_code and
343 previous_previous_code.string.endswith(
344 '.prototype'))
345
346 if (function.has_this and function.doc and
347 not function.doc.HasFlag('this') and
348 not function.is_constructor and
349 not function.is_interface and
350 '.prototype.' not in function.name and
351 not prototype_object_literal):
352 self._HandleError(
353 errors.MISSING_JSDOC_TAG_THIS,
354 'Missing @this JsDoc in function referencing "this". ('
355 'this usually means you are trying to reference "this" in '
356 'a static function, or you have forgotten to mark a '
357 'constructor with @constructor)',
358 function.doc.end_token, position=Position.AtBeginning())
359
360 elif token.type == Type.IDENTIFIER:
361 if token.string == 'goog.inherits' and not state.InFunction():
362 if state.GetLastNonSpaceToken().line_number == token.line_number:
363 self._HandleError(
364 errors.MISSING_LINE,
365 'Missing newline between constructor and goog.inherits',
366 token,
367 position=Position.AtBeginning())
368
369 extra_space = state.GetLastNonSpaceToken().next
370 while extra_space != token:
371 if extra_space.type == Type.BLANK_LINE:
372 self._HandleError(
373 errors.EXTRA_LINE,
374 'Extra line between constructor and goog.inherits',
375 extra_space)
376 extra_space = extra_space.next
377
378 # TODO(robbyw): Test the last function was a constructor.
379 # TODO(robbyw): Test correct @extends and @implements documentation.
380
381 elif (token.string == 'goog.provide' and
382 not state.InFunction() and
383 namespaces_info is not None):
384 namespace = tokenutil.GetStringAfterToken(token)
385
386 # Report extra goog.provide statement.
387 if not namespace or namespaces_info.IsExtraProvide(token):
388 if not namespace:
389 msg = 'Empty namespace in goog.provide'
390 else:
391 msg = 'Unnecessary goog.provide: ' + namespace
392
393 # Hint to user if this is a Test namespace.
394 if namespace.endswith('Test'):
395 msg += (' *Test namespaces must be mentioned in the '
396 'goog.setTestOnly() call')
397
398 self._HandleError(
399 errors.EXTRA_GOOG_PROVIDE,
400 msg,
401 token, position=Position.AtBeginning())
402
403 if namespaces_info.IsLastProvide(token):
404 # Report missing provide statements after the last existing provide.
405 missing_provides = namespaces_info.GetMissingProvides()
406 if missing_provides:
407 self._ReportMissingProvides(
408 missing_provides,
409 tokenutil.GetLastTokenInSameLine(token).next,
410 False)
411
412 # If there are no require statements, missing requires should be
413 # reported after the last provide.
414 if not namespaces_info.GetRequiredNamespaces():
415 missing_requires, illegal_alias_statements = (
416 namespaces_info.GetMissingRequires())
417 if missing_requires:
418 self._ReportMissingRequires(
419 missing_requires,
420 tokenutil.GetLastTokenInSameLine(token).next,
421 True)
422 if illegal_alias_statements:
423 self._ReportIllegalAliasStatement(illegal_alias_statements)
424
425 elif (token.string == 'goog.require' and
426 not state.InFunction() and
427 namespaces_info is not None):
428 namespace = tokenutil.GetStringAfterToken(token)
429
430 # If there are no provide statements, missing provides should be
431 # reported before the first require.
432 if (namespaces_info.IsFirstRequire(token) and
433 not namespaces_info.GetProvidedNamespaces()):
434 missing_provides = namespaces_info.GetMissingProvides()
435 if missing_provides:
436 self._ReportMissingProvides(
437 missing_provides,
438 tokenutil.GetFirstTokenInSameLine(token),
439 True)
440
441 # Report extra goog.require statement.
442 if not namespace or namespaces_info.IsExtraRequire(token):
443 if not namespace:
444 msg = 'Empty namespace in goog.require'
445 else:
446 msg = 'Unnecessary goog.require: ' + namespace
447
448 self._HandleError(
449 errors.EXTRA_GOOG_REQUIRE,
450 msg,
451 token, position=Position.AtBeginning())
452
453 # Report missing goog.require statements.
454 if namespaces_info.IsLastRequire(token):
455 missing_requires, illegal_alias_statements = (
456 namespaces_info.GetMissingRequires())
457 if missing_requires:
458 self._ReportMissingRequires(
459 missing_requires,
460 tokenutil.GetLastTokenInSameLine(token).next,
461 False)
462 if illegal_alias_statements:
463 self._ReportIllegalAliasStatement(illegal_alias_statements)
464
465 elif token.type == Type.OPERATOR:
466 last_in_line = token.IsLastInLine()
467 # If the token is unary and appears to be used in a unary context
468 # it's ok. Otherwise, if it's at the end of the line or immediately
469 # before a comment, it's ok.
470 # Don't report an error before a start bracket - it will be reported
471 # by that token's space checks.
472 if (not token.metadata.IsUnaryOperator() and not last_in_line
473 and not token.next.IsComment()
474 and not token.next.IsOperator(',')
475 and not tokenutil.IsDot(token)
476 and token.next.type not in (Type.WHITESPACE, Type.END_PAREN,
477 Type.END_BRACKET, Type.SEMICOLON,
478 Type.START_BRACKET)):
479 self._HandleError(
480 errors.MISSING_SPACE,
481 'Missing space after "%s"' % token.string,
482 token,
483 position=Position.AtEnd(token.string))
484 elif token.type == Type.WHITESPACE:
485 first_in_line = token.IsFirstInLine()
486 last_in_line = token.IsLastInLine()
487 # Check whitespace length if it's not the first token of the line and
488 # if it's not immediately before a comment.
489 if not last_in_line and not first_in_line and not token.next.IsComment():
490 # Ensure there is no space after opening parentheses.
491 if (token.previous.type in (Type.START_PAREN, Type.START_BRACKET,
492 Type.FUNCTION_NAME)
493 or token.next.type == Type.START_PARAMETERS):
494 self._HandleError(
495 errors.EXTRA_SPACE,
496 'Extra space after "%s"' % token.previous.string,
497 token,
498 position=Position.All(token.string))
499 elif token.type == Type.SEMICOLON:
500 previous_token = tokenutil.SearchExcept(token, Type.NON_CODE_TYPES,
501 reverse=True)
502 if not previous_token:
503 self._HandleError(
504 errors.REDUNDANT_SEMICOLON,
505 'Semicolon without any statement',
506 token,
507 position=Position.AtEnd(token.string))
508 elif (previous_token.type == Type.KEYWORD and
509 previous_token.string not in ['break', 'continue', 'return']):
510 self._HandleError(
511 errors.REDUNDANT_SEMICOLON,
512 ('Semicolon after \'%s\' without any statement.'
513 ' Looks like an error.' % previous_token.string),
514 token,
515 position=Position.AtEnd(token.string))
516
517 def _CheckUnusedLocalVariables(self, token, state):
518 """Checks for unused local variables in function blocks.
519
520 Args:
521 token: The token to check.
522 state: The state tracker.
523 """
524 # We don't use state.InFunction because that disregards scope functions.
525 in_function = state.FunctionDepth() > 0
526 if token.type == Type.SIMPLE_LVALUE or token.type == Type.IDENTIFIER:
527 if in_function:
528 identifier = token.string
529 # Check whether the previous token was var.
530 previous_code_token = tokenutil.CustomSearch(
531 token,
532 lambda t: t.type not in Type.NON_CODE_TYPES,
533 reverse=True)
534 if previous_code_token and previous_code_token.IsKeyword('var'):
535 # Add local variable declaration to the top of the unused locals
536 # stack.
537 self._unused_local_variables_by_scope[-1][identifier] = token
538 elif token.type == Type.IDENTIFIER:
539 # This covers most cases where the variable is used as an identifier.
540 self._MarkLocalVariableUsed(token.string)
541 elif token.type == Type.SIMPLE_LVALUE and '.' in identifier:
542 # This covers cases where a value is assigned to a property of the
543 # variable.
544 self._MarkLocalVariableUsed(token.string)
545 elif token.type == Type.START_BLOCK:
546 if in_function and state.IsFunctionOpen():
547 # Push a new map onto the stack
548 self._unused_local_variables_by_scope.append({})
549 elif token.type == Type.END_BLOCK:
550 if state.IsFunctionClose():
551 # Pop the stack and report any remaining locals as unused.
552 unused_local_variables = self._unused_local_variables_by_scope.pop()
553 for unused_token in unused_local_variables.values():
554 self._HandleError(
555 errors.UNUSED_LOCAL_VARIABLE,
556 'Unused local variable: %s.' % unused_token.string,
557 unused_token)
558 elif token.type == Type.DOC_FLAG:
559 # Flags that use aliased symbols should be counted.
560 flag = token.attached_object
561 js_type = flag and flag.jstype
562 if flag and flag.flag_type in state.GetDocFlag().HAS_TYPE and js_type:
563 self._MarkAliasUsed(js_type)
564
565 def _MarkAliasUsed(self, js_type):
566 """Marks aliases in a type as used.
567
568 Recursively iterates over all subtypes in a jsdoc type annotation and
569 tracks usage of aliased symbols (which may be local variables).
570 Marks the local variable as used in the scope nearest to the current
571 scope that matches the given token.
572
573 Args:
574 js_type: The jsdoc type, a typeannotation.TypeAnnotation object.
575 """
576 if js_type.alias:
577 self._MarkLocalVariableUsed(js_type.identifier)
578 for sub_type in js_type.IterTypes():
579 self._MarkAliasUsed(sub_type)
580
581 def _MarkLocalVariableUsed(self, identifier):
582 """Marks the local variable as used in the relevant scope.
583
584 Marks the local variable in the scope nearest to the current scope that
585 matches the given identifier as used.
586
587 Args:
588 identifier: The identifier representing the potential usage of a local
589 variable.
590 """
591 identifier = identifier.split('.', 1)[0]
592 # Find the first instance of the identifier in the stack of function scopes
593 # and mark it used.
594 for unused_local_variables in reversed(
595 self._unused_local_variables_by_scope):
596 if identifier in unused_local_variables:
597 del unused_local_variables[identifier]
598 break
599
600 def _ReportMissingProvides(self, missing_provides, token, need_blank_line):
601 """Reports missing provide statements to the error handler.
602
603 Args:
604 missing_provides: A dictionary of string(key) and integer(value) where
605 each string(key) is a namespace that should be provided, but is not
606 and integer(value) is first line number where it's required.
607 token: The token where the error was detected (also where the new provides
608 will be inserted.
609 need_blank_line: Whether a blank line needs to be inserted after the new
610 provides are inserted. May be True, False, or None, where None
611 indicates that the insert location is unknown.
612 """
613
614 missing_provides_msg = 'Missing the following goog.provide statements:\n'
615 missing_provides_msg += '\n'.join(['goog.provide(\'%s\');' % x for x in
616 sorted(missing_provides)])
617 missing_provides_msg += '\n'
618
619 missing_provides_msg += '\nFirst line where provided: \n'
620 missing_provides_msg += '\n'.join(
621 [' %s : line %d' % (x, missing_provides[x]) for x in
622 sorted(missing_provides)])
623 missing_provides_msg += '\n'
624
625 self._HandleError(
626 errors.MISSING_GOOG_PROVIDE,
627 missing_provides_msg,
628 token, position=Position.AtBeginning(),
629 fix_data=(missing_provides.keys(), need_blank_line))
630
631 def _ReportMissingRequires(self, missing_requires, token, need_blank_line):
632 """Reports missing require statements to the error handler.
633
634 Args:
635 missing_requires: A dictionary of string(key) and integer(value) where
636 each string(key) is a namespace that should be required, but is not
637 and integer(value) is first line number where it's required.
638 token: The token where the error was detected (also where the new requires
639 will be inserted.
640 need_blank_line: Whether a blank line needs to be inserted before the new
641 requires are inserted. May be True, False, or None, where None
642 indicates that the insert location is unknown.
643 """
644
645 missing_requires_msg = 'Missing the following goog.require statements:\n'
646 missing_requires_msg += '\n'.join(['goog.require(\'%s\');' % x for x in
647 sorted(missing_requires)])
648 missing_requires_msg += '\n'
649
650 missing_requires_msg += '\nFirst line where required: \n'
651 missing_requires_msg += '\n'.join(
652 [' %s : line %d' % (x, missing_requires[x]) for x in
653 sorted(missing_requires)])
654 missing_requires_msg += '\n'
655
656 self._HandleError(
657 errors.MISSING_GOOG_REQUIRE,
658 missing_requires_msg,
659 token, position=Position.AtBeginning(),
660 fix_data=(missing_requires.keys(), need_blank_line))
661
662 def _ReportIllegalAliasStatement(self, illegal_alias_statements):
663 """Reports alias statements that would need a goog.require."""
664 for namespace, token in illegal_alias_statements.iteritems():
665 self._HandleError(
666 errors.ALIAS_STMT_NEEDS_GOOG_REQUIRE,
667 'The alias definition would need the namespace \'%s\' which is not '
668 'required through any other symbol.' % namespace,
669 token, position=Position.AtBeginning())
670
671 def Finalize(self, state):
672 """Perform all checks that need to occur after all lines are processed."""
673 # Call the base class's Finalize function.
674 super(JavaScriptLintRules, self).Finalize(state)
675
676 if error_check.ShouldCheck(Rule.UNUSED_PRIVATE_MEMBERS):
677 # Report an error for any declared private member that was never used.
678 unused_private_members = (self._declared_private_members -
679 self._used_private_members)
680
681 for variable in unused_private_members:
682 token = self._declared_private_member_tokens[variable]
683 self._HandleError(errors.UNUSED_PRIVATE_MEMBER,
684 'Unused private member: %s.' % token.string,
685 token)
686
687 # Clear state to prepare for the next file.
688 self._declared_private_member_tokens = {}
689 self._declared_private_members = set()
690 self._used_private_members = set()
691
692 namespaces_info = self._namespaces_info
693 if namespaces_info is not None:
694 # If there are no provide or require statements, missing provides and
695 # requires should be reported on line 1.
696 if (not namespaces_info.GetProvidedNamespaces() and
697 not namespaces_info.GetRequiredNamespaces()):
698 missing_provides = namespaces_info.GetMissingProvides()
699 if missing_provides:
700 self._ReportMissingProvides(
701 missing_provides, state.GetFirstToken(), None)
702
703 missing_requires, illegal_alias = namespaces_info.GetMissingRequires()
704 if missing_requires:
705 self._ReportMissingRequires(
706 missing_requires, state.GetFirstToken(), None)
707 if illegal_alias:
708 self._ReportIllegalAliasStatement(illegal_alias)
709
710 self._CheckSortedRequiresProvides(state.GetFirstToken())
711
712 def _CheckSortedRequiresProvides(self, token):
713 """Checks that all goog.require and goog.provide statements are sorted.
714
715 Note that this method needs to be run after missing statements are added to
716 preserve alphabetical order.
717
718 Args:
719 token: The first token in the token stream.
720 """
721 sorter = requireprovidesorter.RequireProvideSorter()
722 first_provide_token = sorter.CheckProvides(token)
723 if first_provide_token:
724 new_order = sorter.GetFixedProvideString(first_provide_token)
725 self._HandleError(
726 errors.GOOG_PROVIDES_NOT_ALPHABETIZED,
727 'goog.provide classes must be alphabetized. The correct code is:\n' +
728 new_order,
729 first_provide_token,
730 position=Position.AtBeginning(),
731 fix_data=first_provide_token)
732
733 first_require_token = sorter.CheckRequires(token)
734 if first_require_token:
735 new_order = sorter.GetFixedRequireString(first_require_token)
736 self._HandleError(
737 errors.GOOG_REQUIRES_NOT_ALPHABETIZED,
738 'goog.require classes must be alphabetized. The correct code is:\n' +
739 new_order,
740 first_require_token,
741 position=Position.AtBeginning(),
742 fix_data=first_require_token)
743
744 def GetLongLineExceptions(self):
745 """Gets a list of regexps for lines which can be longer than the limit.
746
747 Returns:
748 A list of regexps, used as matches (rather than searches).
749 """
750 return [
751 re.compile(r'((var|let|const) .+\s*=\s*)?goog\.require\(.+\);?\s*$'),
752 re.compile(r'goog\.(forwardDeclare|module|provide|setTestOnly)'
753 r'\(.+\);?\s*$'),
754 re.compile(r'[\s/*]*@visibility\s*{.*}[\s*/]*$'),
755 ]
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698