diagnostic_filter.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # Copyright (C) 2016 YouCompleteMe contributors
  2. #
  3. # This file is part of YouCompleteMe.
  4. #
  5. # YouCompleteMe is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # YouCompleteMe is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  17. from __future__ import unicode_literals
  18. from __future__ import print_function
  19. from __future__ import division
  20. from __future__ import absolute_import
  21. # Not installing aliases from python-future; it's unreliable and slow.
  22. from builtins import * # noqa
  23. from future.utils import iterkeys, iteritems
  24. from ycm import vimsupport
  25. import re
  26. class DiagnosticFilter( object ):
  27. def __init__( self, config_or_filters ):
  28. if isinstance( config_or_filters, list ):
  29. self._filters = config_or_filters
  30. else:
  31. self._filters = _CompileFilters( config_or_filters )
  32. def IsAllowed( self, diagnostic ):
  33. # NOTE: a diagnostic IsAllowed() ONLY if NO filters match it
  34. for filterMatches in self._filters:
  35. if filterMatches( diagnostic ):
  36. return False
  37. return True
  38. def SubsetForTypes( self, filetypes ):
  39. """Return a sub-filter limited to the given filetypes"""
  40. # NOTE: actually, this class is already filtered
  41. return self
  42. @staticmethod
  43. def CreateFromOptions( user_options ):
  44. all_filters = user_options[ 'filter_diagnostics' ]
  45. compiled_by_type = {}
  46. for type_spec, filter_value in iteritems( all_filters ):
  47. filetypes = [ type_spec ]
  48. if type_spec.find( ',' ) != -1:
  49. filetypes = type_spec.split( ',' )
  50. for filetype in filetypes:
  51. compiled_by_type[ filetype ] = _CompileFilters( filter_value )
  52. return _MasterDiagnosticFilter( compiled_by_type )
  53. class _MasterDiagnosticFilter( object ):
  54. def __init__( self, all_filters ):
  55. self._all_filters = all_filters
  56. self._cache = {}
  57. def IsAllowed( self, diagnostic ):
  58. # NOTE: in this class's implementation, we ask vimsupport for
  59. # the current filetypes and delegate automatically; it is probably,
  60. # more efficient, however, to call SubsetForTypes() and reuse
  61. # the returned DiagnosticFilter if it will be checked repeatedly.
  62. filetypes = vimsupport.CurrentFiletypes()
  63. return self.SubsetForTypes( filetypes ).IsAllowed( diagnostic )
  64. def SubsetForTypes( self, filetypes ):
  65. # check cache
  66. cache_key = ','.join( filetypes )
  67. cached = self._cache.get( cache_key )
  68. if cached is not None:
  69. return cached
  70. # build a new DiagnosticFilter merging all filters
  71. # for the provided filetypes
  72. spec = []
  73. for filetype in filetypes:
  74. type_specific = self._all_filters.get( filetype, [] )
  75. spec.extend( type_specific )
  76. new_filter = DiagnosticFilter( spec )
  77. self._cache[ cache_key ] = new_filter
  78. return new_filter
  79. def _ListOf( config_entry ):
  80. if isinstance( config_entry, list ):
  81. return config_entry
  82. if config_entry is None:
  83. return []
  84. return [ config_entry ]
  85. def CompileRegex( raw_regex ):
  86. pattern = re.compile( raw_regex, re.IGNORECASE )
  87. def FilterRegex( diagnostic ):
  88. return pattern.search( diagnostic[ 'text' ] ) is not None
  89. return FilterRegex
  90. def CompileLevel( level ):
  91. # valid kinds are WARNING and ERROR;
  92. # expected input levels are `warning` and `error`
  93. # NOTE: we don't validate the input...
  94. expected_kind = level.upper()
  95. def FilterLevel( diagnostic ):
  96. return diagnostic[ 'kind' ] == expected_kind
  97. return FilterLevel
  98. FILTER_COMPILERS = { 'regex' : CompileRegex,
  99. 'level' : CompileLevel }
  100. def _CompileFilters( config ):
  101. """Given a filter config dictionary, return a list of compiled filters"""
  102. filters = []
  103. for filter_type in iterkeys( config ):
  104. compiler = FILTER_COMPILERS.get( filter_type )
  105. if compiler is not None:
  106. for filter_config in _ListOf( config[ filter_type ] ):
  107. compiledFilter = compiler( filter_config )
  108. filters.append( compiledFilter )
  109. return filters