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
18import re
19
20
21class DiagnosticFilter:
22  def __init__( self, config_or_filters ):
23    self._filters : list = config_or_filters
24
25
26  def IsAllowed( self, diagnostic ):
27    return not any( filterMatches( diagnostic )
28                    for filterMatches in self._filters )
29
30
31  @staticmethod
32  def CreateFromOptions( user_options ):
33    all_filters = user_options[ 'filter_diagnostics' ]
34    compiled_by_type = {}
35    for type_spec, filter_value in all_filters.items():
36      filetypes = type_spec.split( ',' )
37      for filetype in filetypes:
38        compiled_by_type[ filetype ] = _CompileFilters( filter_value )
39
40    return _MasterDiagnosticFilter( compiled_by_type )
41
42
43class _MasterDiagnosticFilter:
44
45  def __init__( self, all_filters ):
46    self._all_filters = all_filters
47    self._cache = {}
48
49
50  def SubsetForTypes( self, filetypes ):
51    # check cache
52    cache_key = ','.join( filetypes )
53    cached = self._cache.get( cache_key )
54    if cached is not None:
55      return cached
56
57    # build a new DiagnosticFilter merging all filters
58    #  for the provided filetypes
59    spec = []
60    for filetype in filetypes:
61      type_specific = self._all_filters.get( filetype, [] )
62      spec.extend( type_specific )
63
64    new_filter = DiagnosticFilter( spec )
65    self._cache[ cache_key ] = new_filter
66    return new_filter
67
68
69def _ListOf( config_entry ):
70  if isinstance( config_entry, list ):
71    return config_entry
72
73  return [ config_entry ]
74
75
76def CompileRegex( raw_regex ):
77  pattern = re.compile( raw_regex, re.IGNORECASE )
78
79  def FilterRegex( diagnostic ):
80    return pattern.search( diagnostic[ 'text' ] ) is not None
81
82  return FilterRegex
83
84
85def CompileLevel( level ):
86  # valid kinds are WARNING and ERROR;
87  #  expected input levels are `warning` and `error`
88  # NOTE: we don't validate the input...
89  expected_kind = level.upper()
90
91  def FilterLevel( diagnostic ):
92    return diagnostic[ 'kind' ] == expected_kind
93
94  return FilterLevel
95
96
97FILTER_COMPILERS = { 'regex' : CompileRegex,
98                     'level' : CompileLevel }
99
100
101def _CompileFilters( config ):
102  """Given a filter config dictionary, return a list of compiled filters"""
103  filters = []
104
105  for filter_type, filter_pattern in config.items():
106    compiler = FILTER_COMPILERS.get( filter_type )
107
108    if compiler is not None:
109      for filter_config in _ListOf( filter_pattern ):
110        compiledFilter = compiler( filter_config )
111        filters.append( compiledFilter )
112
113  return filters
114