Sfoglia il codice sorgente

Refactor filter spec to filter_diagnostics; update tests, README

dhleong 8 anni fa
parent
commit
bb793826b6

+ 32 - 0
README.md

@@ -1803,6 +1803,38 @@ Default: `1`
 let g:ycm_echo_current_diagnostic = 1
 ```
 
+### The `g:ycm_filter_diagnostics` option
+
+This option controls which diagnostics will be rendered by YCM. This option holds
+a dictionary of key-values, where the keys are Vim's filetype strings delimited by
+commas and values are dictionaries describing the filter. 
+
+A filter is a dictionary of key-values, where the keys are the type of filter,
+and the value is a list of arguments to that filter. In the case of just a single 
+item in the list, you may omit the brackets and just provide the argument directly.
+If any filter matches a diagnostic, it will be dropped and YCM will not render it.
+
+The following filter types are supported:
+
+- "regex": Accepts a string [regular expression][python-re]. This type matches
+when the regex (treated as case-insensitive) is found in the diagnostic text.
+- "level": Accepts a string level, either "warning" or "error." This type matches
+when the diagnostic has the same level.
+
+NOTE: The regex syntax is **NOT** Vim's, it's [Python's][python-re].
+
+Default: `{}`
+
+```viml
+let g:ycm_filter_diagnostics = {
+  \ "java": {
+  \      "regex": [ ".*taco.*", ... ],
+  \      "level": "error",
+  \      ...
+  \    }
+  \ }
+```
+
 ### The `g:ycm_always_populate_location_list` option
 
 When this option is set, YCM will populate the location list automatically every

+ 0 - 4
plugin/youcompleteme.vim

@@ -121,10 +121,6 @@ let g:ycm_warning_symbol =
       \ get( g:, 'ycm_warning_symbol',
       \ get( g:, 'syntastic_warning_symbol', '>>' ) )
 
-let g:ycm_quiet_messages =
-      \ get( g:, 'ycm_quiet_messages',
-      \ get( g:, 'syntastic_quiet_messages', {} ) )
-
 let g:ycm_goto_buffer_command =
       \ get( g:, 'ycm_goto_buffer_command', 'same-buffer' )
 

+ 23 - 24
python/ycm/diagnostic_filter.py

@@ -23,7 +23,7 @@ from future import standard_library
 standard_library.install_aliases()
 from builtins import *  # noqa
 
-from future.utils import iterkeys
+from future.utils import iterkeys, iteritems
 import re
 
 
@@ -32,23 +32,18 @@ class DiagnosticFilter( object ):
     self._filters = []
 
     for filter_type in iterkeys( config ):
-      wrapper = _AsIs
       actual_filter_type = filter_type
-
-      if filter_type[ 0 ] == '!':
-        wrapper = _Not
-        filter_type = filter_type[ 1 : ]
       compiler = FILTER_COMPILERS.get( filter_type )
 
       if compiler is not None:
         for filter_config in _ListOf( config[ actual_filter_type ] ):
-          fn = wrapper( compiler( filter_config ) )
+          fn = compiler( filter_config )
           self._filters.append( fn )
 
 
   def IsAllowed( self, diagnostic ):
     # NOTE: a diagnostic IsAllowed() ONLY if
-    #  no filters match it
+    #  NO filters match it
     for f in self._filters:
       if f( diagnostic ):
         return False
@@ -58,30 +53,34 @@ class DiagnosticFilter( object ):
 
   @staticmethod
   def from_filetype( user_options, filetypes ):
-    base = dict( user_options.get( 'quiet_messages', {} ) )
+    spec = {}
+    all_filters = dict( user_options.get( 'filter_diagnostics', {} ) )
+    for typeSpec, filterValue in iteritems( dict( all_filters ) ):
+      if typeSpec.find(',') != -1:
+        for ft in typeSpec.split(','):
+          all_filters[ ft ] = filterValue
 
     for filetype in filetypes:
-      type_specific = user_options.get( filetype + '_quiet_messages', {} )
-      base.update( type_specific )
-    return DiagnosticFilter( base )
+      type_specific = all_filters.get( filetype, {} )
+      spec = _Merge( spec, type_specific )
+    return DiagnosticFilter( spec )
 
 
 def _ListOf( config_entry ):
   if isinstance( config_entry, list ):
     return config_entry
 
-  return [ config_entry ]
-
+  if config_entry is None:
+    return []
 
-def _AsIs( fn ):
-  return fn
+  return [ config_entry ]
 
 
-def _Not( fn ):
-  def Inverted( diagnostic ):
-    return not fn( diagnostic )
+def _Merge( into, other ):
+  for k in iterkeys( other ):
+    into[k] = _ListOf( into.get( k ) ) + _ListOf( other[ k ] )
 
-  return Inverted
+  return into
 
 
 def _CompileRegex( raw_regex ):
@@ -95,9 +94,9 @@ def _CompileRegex( raw_regex ):
 
 def _CompileLevel( level ):
   # valid kinds are WARNING and ERROR;
-  #  expected input levels are `warnings` and `errors`
+  #  expected input levels are `warning` and `error`
   # NB: we don't validate the input...
-  expected_kind = level.upper()[ : -1 ]
+  expected_kind = level.upper()
 
   def FilterLevel( diagnostic ):
     return diagnostic[ 'kind' ] == expected_kind
@@ -105,5 +104,5 @@ def _CompileLevel( level ):
   return FilterLevel
 
 
-FILTER_COMPILERS  = { 'regex' : _CompileRegex,
-                      'level' : _CompileLevel }
+FILTER_COMPILERS = { 'regex' : _CompileRegex,
+                     'level' : _CompileLevel }

+ 56 - 55
python/ycm/tests/diagnostic_filter_tests.py

@@ -45,95 +45,96 @@ def _assert_rejects( filter, text ):
   _assert_accept_equals( filter, text, False )
 
 
-class ConfigPriority_test():
+def _JavaFilter( config ):
+  return { 'filter_diagnostics' : { 'java': config } }
 
-  def ConfigPriority_Global_test( self ):
-    opts = { 'quiet_messages': { 'regex': 'taco' } }
-    f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
-    _assert_rejects( f, 'This is a Taco' )
-    _assert_accepts( f, 'This is a Burrito' )
+def RegexFilter_test():
+  opts = _JavaFilter( { 'regex' : 'taco' } )
+  f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
+  _assert_rejects( f, 'This is a Taco' )
+  _assert_accepts( f, 'This is a Burrito' )
 
-  def ConfigPriority_Filetype_test( self ):
-    opts = { 'quiet_messages'      : {},
-             'java_quiet_messages' : { 'regex': 'taco' } }
-    f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
-
-    _assert_rejects( f, 'This is a Taco' )
-    _assert_accepts( f, 'This is a Burrito' )
 
+class ListOrSingle_test():
+  # NB: we already test the single config above
 
-  def ConfigPriority_FiletypeOverridesGlobal_test( self ):
+  def ListOrSingle_SingleList_test( self ):
     # NB: if the filetype doesn't override the global,
     #  we would reject burrito and accept taco
-    opts = { 'quiet_messages'      : { 'regex': 'burrito'},
-             'java_quiet_messages' : { 'regex': 'taco' } }
+    opts = _JavaFilter( { 'regex' : [ 'taco' ] }  )
     f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
     _assert_rejects( f, 'This is a Taco' )
     _assert_accepts( f, 'This is a Burrito' )
 
 
-  def ConfigPriority_FiletypeDisablesGlobal_test( self ):
+  def ListOrSingle_MultiList_test( self ):
     # NB: if the filetype doesn't override the global,
     #  we would reject burrito and accept taco
-    opts = { 'quiet_messages'      : { 'regex': 'taco'},
-             'java_quiet_messages' : { 'regex': [] } }
+    opts = _JavaFilter( { 'regex' : [ 'taco', 'burrito' ] } )
     f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
-    _assert_accepts( f, 'This is a Taco' )
-    _assert_accepts( f, 'This is a Burrito' )
+    _assert_rejects( f, 'This is a Taco' )
+    _assert_rejects( f, 'This is a Burrito' )
 
 
-class ListOrSingle_test():
-  # NB: we already test the single config above
+class Level_test():
 
-  def ListOrSingle_SingleList_test( self ):
-    # NB: if the filetype doesn't override the global,
-    #  we would reject burrito and accept taco
-    opts = { 'quiet_messages' : { 'regex': [ 'taco' ] } }
+  def Level_warnings_test( self ):
+    opts = _JavaFilter( { 'level' : 'warning' } )
     f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
-    _assert_rejects( f, 'This is a Taco' )
-    _assert_accepts( f, 'This is a Burrito' )
+    _assert_rejects( f, { 'text' : 'This is an unimportant taco',
+                          'kind' : 'WARNING' } )
+    _assert_accepts( f, { 'text' : 'This taco will be shown',
+                          'kind' : 'ERROR' } )
 
 
-  def ListOrSingle_MultiList_test( self ):
-    # NB: if the filetype doesn't override the global,
-    #  we would reject burrito and accept taco
-    opts = { 'quiet_messages' : { 'regex': [ 'taco', 'burrito' ] } }
+  def Level_errors_test( self ):
+    opts = _JavaFilter( { 'level' : 'error' } )
     f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
-    _assert_rejects( f, 'This is a Taco' )
-    _assert_rejects( f, 'This is a Burrito' )
+    _assert_accepts( f, { 'text' : 'This is an IMPORTANT taco',
+                          'kind' : 'WARNING' } )
+    _assert_rejects( f, { 'text' : 'This taco will NOT be shown',
+                          'kind' : 'ERROR' } )
 
 
-def Invert_test():
-    opts = { 'quiet_messages' : { '!regex': 'taco' } }
-    f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
+def MultipleFilterTypesTypeTest_test():
 
-    _assert_accepts( f, 'This is a Taco' )
-    _assert_rejects( f, 'This is a Burrito' )
+  opts = _JavaFilter( { 'regex' : '.*taco.*',
+                        'level' : 'warning' } )
+  f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
 
+  _assert_rejects( f, { 'text' : 'This is an unimportant taco',
+                        'kind' : 'WARNING' } )
+  _assert_rejects( f, { 'text' : 'This taco will NOT be shown',
+                        'kind' : 'ERROR' } )
+  _assert_accepts( f, { 'text' : 'This burrito WILL be shown',
+                        'kind' : 'ERROR' } )
 
-class Level_test():
 
-  def Level_warnings_test( self ):
-    opts = { 'quiet_messages' : { 'level': 'warnings' } }
-    f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
+def MergeMultipleFiletypes_test():
 
-    _assert_rejects( f, { 'text': 'This is an unimportant taco',
-                          'kind': 'WARNING' } )
-    _assert_accepts( f, { 'text': 'This taco will be shown',
-                          'kind': 'ERROR' } )
+  opts = { 'filter_diagnostics' : {
+    'java' : { 'regex' : '.*taco.*' },
+    'xml'  : { 'regex' : '.*burrito.*' } } }
 
+  f = DiagnosticFilter.from_filetype( opts, [ 'java', 'xml' ] )
 
-  def Level_errors_test( self ):
-    opts = { 'quiet_messages' : { 'level': 'errors' } }
-    f = DiagnosticFilter.from_filetype( opts, [ 'java' ] )
+  _assert_rejects( f, 'This is a Taco' )
+  _assert_rejects( f, 'This is a Burrito' )
+  _assert_accepts( f, 'This is some Nachos' )
+
+
+def CommaSeparatedFiletypes_test():
+
+  opts = { 'filter_diagnostics' : {
+    'java,c,cs' : { 'regex' : '.*taco.*' } } }
+
+  f = DiagnosticFilter.from_filetype( opts, [ 'cs' ] )
 
-    _assert_accepts( f, { 'text': 'This is an IMPORTANT taco',
-                          'kind': 'WARNING' } )
-    _assert_rejects( f, { 'text': 'This taco will NOT be shown',
-                          'kind': 'ERROR' } )
+  _assert_rejects( f, 'This is a Taco' )
+  _assert_accepts( f, 'This is a Burrito' )