2828
2929import ast
3030import difflib
31+ import functools
3132import collections
3233import distutils .sysconfig
3334import fnmatch
@@ -140,10 +141,11 @@ def star_import_usage_undefined_name(messages):
140141
141142
142143def unused_variable_line_numbers (messages ):
143- """Yield line numbers of unused variables."""
144- for message in messages :
145- if isinstance (message , pyflakes .messages .UnusedVariable ):
146- yield message .lineno
144+ """Dict of line numbers to unused variables."""
145+ return {
146+ m .lineno : frozenset (m .message_args )
147+ for m in messages
148+ }
147149
148150
149151def duplicate_key_line_numbers (messages , source ):
@@ -372,10 +374,11 @@ def filter_code(source, additional_imports=None,
372374 marked_star_import_line_numbers = frozenset ()
373375
374376 if remove_unused_variables :
375- marked_variable_line_numbers = frozenset (
376- unused_variable_line_numbers (messages ))
377+ marked_variable_line_numbers = (
378+ unused_variable_line_numbers (messages )
379+ )
377380 else :
378- marked_variable_line_numbers = frozenset ()
381+ marked_variable_line_numbers = {}
379382
380383 if remove_duplicate_keys :
381384 marked_key_line_numbers = frozenset (
@@ -388,6 +391,7 @@ def filter_code(source, additional_imports=None,
388391 sio = io .StringIO (source )
389392 previous_line = ''
390393 for line_number , line in enumerate (sio .readlines (), start = 1 ):
394+ unused_vars = marked_variable_line_numbers .get (line_number )
391395 if '#' in line :
392396 yield line
393397 elif line_number in marked_import_line_numbers :
@@ -397,8 +401,8 @@ def filter_code(source, additional_imports=None,
397401 remove_all_unused_imports = remove_all_unused_imports ,
398402 imports = imports ,
399403 previous_line = previous_line )
400- elif line_number in marked_variable_line_numbers :
401- yield filter_unused_variable (line )
404+ elif unused_vars :
405+ yield filter_unused_variable (line , unused_vars )
402406 elif line_number in marked_key_line_numbers :
403407 yield filter_duplicate_key (line , line_messages [line_number ],
404408 line_number , marked_key_line_numbers ,
@@ -453,28 +457,67 @@ def filter_unused_import(line, unused_module, remove_all_unused_imports,
453457 get_line_ending (line ))
454458
455459
456- def filter_unused_variable (line , previous_line = '' ):
460+ def _remove_one_assignment_target (unused_vars , line ):
461+ try :
462+ parsed = ast .parse (line )
463+ except SyntaxError :
464+ return line
465+
466+ assignment = parsed .body [0 ]
467+ if not isinstance (assignment , ast .Assign ):
468+ return line
469+
470+ targets = assignment .targets
471+ for target in assignment .targets :
472+ if not isinstance (target , ast .Name ):
473+ continue
474+ name = target .id
475+ if name not in unused_vars :
476+ continue
477+ offset = target .col_offset
478+ return line [:offset ] + re .sub (
479+ r'\A\s*' + re .escape (name ) + r'\s*=\s*' ,
480+ '' , line [offset :],
481+ count = 1 ,
482+ )
483+ return line
484+
485+
486+ def _fix (fn , value ):
487+ """
488+ Apply fn to its output until it coverges
489+ """
490+ while True :
491+ new_value = fn (value )
492+ if new_value == value :
493+ return new_value
494+ value = new_value
495+
496+
497+ def filter_unused_variable (line , unused_vars ):
457498 """Return line if used, otherwise return None."""
458499 if re .match (EXCEPT_REGEX , line ):
459- return re .sub (r' as \w+:$' , ':' , line , count = 1 )
460- elif multiline_statement (line , previous_line ):
461- return line
462- elif line .count ('=' ) == 1 :
463- split_line = line .split ('=' )
464- assert len (split_line ) == 2
465- value = split_line [1 ].lstrip ()
466- if ',' in split_line [0 ]:
467- return line
468-
469- if is_literal_or_name (value ):
470- # Rather than removing the line, replace with it "pass" to avoid
471- # a possible hanging block with no body.
472- value = 'pass' + get_line_ending (line )
473-
474- return get_indentation (line ) + value
475- else :
500+ assert len (unused_vars ) == 1
501+ unused_e , = unused_vars
502+ return line .replace (
503+ ' as {}:' .format (unused_e ),
504+ ':' ,
505+ 1 ,
506+ )
507+ if multiline_statement (line , '' ):
476508 return line
477509
510+ indentation = get_indentation (line )
511+ line = line [len (indentation ):]
512+ remove = functools .partial (_remove_one_assignment_target , unused_vars )
513+ line = _fix (remove , line )
514+
515+ if is_literal_or_name (line ):
516+ # Rather than removing the line, replace with it "pass" to avoid
517+ # a possible hanging block with no body.
518+ return indentation + 'pass' + get_line_ending (line )
519+ return indentation + line
520+
478521
479522def filter_duplicate_key (line , message , line_number , marked_line_numbers ,
480523 source , previous_line = '' ):
0 commit comments