Skip to content

Commit

Permalink
Add parameterization for #catkin_lint pragmas
Browse files Browse the repository at this point in the history
You can now restrict ignored messages to certain instances.
  • Loading branch information
roehling committed Sep 24, 2024
1 parent fe1e4da commit e310ed9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 21 deletions.
10 changes: 2 additions & 8 deletions src/catkin_lint/checks/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,7 @@ def on_final(info):
if node.tag is ET.Comment:
args = (node.text or "").split()
if args and args[0] == "catkin_lint:" and args[1] in ["ignore_once", "ignore", "report"]:
msg_ids = set([a.upper() for a in args[2:]])
if args[1] == "ignore":
info.ignore_message_ids |= msg_ids
if args[1] == "report":
info.ignore_message_ids -= msg_ids
if args[1] == "ignore_once":
info.ignore_message_ids_once |= msg_ids
info.linter._handle_pragma(info, args[1:])
else:
relevant_deps = info.exec_dep
dep_type = exec_dep_type
Expand All @@ -119,7 +113,7 @@ def on_final(info):
pkg = mo.group(1)
if pkg is not None and pkg != info.manifest.name and info.env.get_package_type(pkg) == PackageType.CATKIN and pkg not in relevant_deps and pkg not in essential_packages:
info.report(WARNING, "LAUNCH_DEPEND", type=dep_type, pkg=pkg, file_location=(src_filename, node.sourceline or 0))
info.ignore_message_ids_once.clear()
info.ignore_messages_once.clear()
except (ET.Error, ValueError) as err:
info.report(WARNING, "PARSE_ERROR", msg=str(err), file_location=(src_filename, 0))

Expand Down
83 changes: 71 additions & 12 deletions src/catkin_lint/linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import operator
import os
import posixpath
import re
import random
import string
from fnmatch import fnmatch
from copy import copy
from collections import defaultdict
from .cmake import ParserContext, argparse as cmake_argparse, CMakeSyntaxError
from .diagnostics import msg, add_user_defined_msg
from .util import abspath
Expand All @@ -46,6 +48,14 @@
NOTICE = 2


def re_fullmatch(a, b):
return re.fullmatch(a, b)


def not_re_fullmatch(a, b):
return not re.fullmatch(a, b)


class Message(object):

def __init__(self, package, file_name, line, level, msg_id, text, description):
Expand Down Expand Up @@ -83,7 +93,7 @@ class PathConstants(object):

class LintInfo(object):

def __init__(self, env):
def __init__(self, env, linter=None):
self.env = env
self.path = None
self.subdir = ""
Expand All @@ -92,8 +102,8 @@ def __init__(self, env):
self.manifest = None
self.file = ""
self.line = 0
self.ignore_message_ids = set()
self.ignore_message_ids_once = set()
self.ignore_messages = defaultdict(list)
self.ignore_messages_once = defaultdict(list)
self.command_loc = {}
self.commands = set()
self.find_packages = set()
Expand All @@ -109,15 +119,54 @@ def __init__(self, env):
self.ignored_messages = []
self.generated_files = set([""])
self.message_level_override = {}
self.linter = linter

def parse_ignore_filter(self, s):
results = defaultdict(list)
for mo in re.finditer(r"(?P<ID>[a-z0-9_-]+)(:?\[(?P<EXPR>[^\]]*)\])?", s, re.IGNORECASE):
msg_id = mo.group("ID").upper()
expr = mo.group("EXPR")
if expr:
clauses = []
for clause in expr.split(","):
clause = clause.strip()
if "!=" in clause:
clauses.append((operator.ne, *[s.strip() for s in clause.split("!=", 1)]))
elif "!~" in clause:
clauses.append((not_re_fullmatch, *[s.strip() for s in clause.split("!~", 1)]))
elif "=" in clause:
clauses.append((operator.eq, *[s.strip() for s in clause.split("=", 1)]))
elif "~" in clause:
clauses.append((re_fullmatch, *[s.strip() for s in clause.split("~", 1)]))
else:
clauses.append((lambda x, y: False, None, None))
results[msg_id].append(clauses)
else:
results[msg_id].append(True)
return results

def report(self, level, msg_id, **kwargs):
def _matches_ignore_filter(expr):
if isinstance(expr, bool):
return expr
for clauses in expr:
if isinstance(clauses, bool) and clauses == True:
return True
for match in clauses:
if not match[0](match[2], kwargs.get(match[1], None)):
break
else:
return True
return False

file_name, line = self.file, self.line
loc = kwargs.get("file_location", None)
if loc:
file_name, line = loc
del kwargs["file_location"]
msg_id, text, description = msg(msg_id, **kwargs)
if msg_id in self.ignore_message_ids or msg_id in self.ignore_message_ids_once:

if _matches_ignore_filter(self.ignore_messages.get(msg_id, False)) or _matches_ignore_filter(self.ignore_messages_once.get(msg_id, False)):
self.ignored_messages.append(Message(
package=self.manifest.name,
file_name=file_name,
Expand Down Expand Up @@ -465,11 +514,21 @@ def _handle_list(self, info, args):
def _handle_pragma(self, info, args):
pragma = args.pop(0)
if pragma == "ignore":
info.ignore_message_ids |= set([a.upper() for a in args])
msgs = info.parse_ignore_filter(" ".join(args))
print(msgs)
for key, value in msgs.items():
info.ignore_messages[key] += value
if pragma == "report":
info.ignore_message_ids -= set([a.upper() for a in args])
msgs = info.parse_ignore_filter(" ".join(args))
for key, value in msgs.items():
if True in value:
info.ignore_messages[key].clear()
else:
info.ignore_messages[key] = [entry for entry in info.ignore_messages[key] if entry not in value]
if pragma == "ignore_once":
info.ignore_message_ids_once |= set([a.upper() for a in args])
msgs = info.parse_ignore_filter(" ".join(args))
for key, value in msgs.items():
info.ignore_messages_once[key] += value
if pragma == "skip":
self._ctx.skip_block()

Expand Down Expand Up @@ -653,11 +712,11 @@ def _parse_file(self, info, filename):
self.execute_hook(info, cmd, args)
info.commands.add(cmd)
info.command_loc[cmd] = info.current_location()
info.ignore_message_ids_once.clear()
info.ignore_messages_once.clear()
finally:
info.file = save_file
info.line = save_line
info.ignore_message_ids_once.clear()
info.ignore_messages_once.clear()
self._ctx = save_ctx

KEYWORD_TO_SEVERITY = {"error": ERROR, "warning": WARNING, "notice": NOTICE}
Expand All @@ -667,18 +726,18 @@ def _get_overrides(self, info, section):
val = section[opt].lower().strip()
opt = opt.upper()
if val == "ignore":
info.ignore_message_ids.add(opt)
info.ignore_messages[opt].append(True)
elif val == "default":
info.message_level_override.pop(opt, None)
info.ignore_message_ids.discard(opt)
info.ignore_messages[opt].clear()
else:
severity = self.KEYWORD_TO_SEVERITY.get(val, None)
if severity is not None:
info.message_level_override[opt] = severity

def lint(self, path, manifest, info=None, config=None):
if info is None:
info = LintInfo(self.env)
info = LintInfo(self.env, linter=self)
if config is not None:
if "*" in config:
self._get_overrides(info, config["*"])
Expand Down
2 changes: 1 addition & 1 deletion test/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def get_cmakelist(filename):
linter._read_file = get_cmakelist
if checks is not None:
linter.require(checks)
info = LintInfo(env)
info = LintInfo(env, linter=linter)
linter.lint(os.path.normpath(package_path), manifest, info=info)
if not indentation:
linter.messages = [m for m in linter.messages if m.id != "INDENTATION"]
Expand Down
48 changes: 48 additions & 0 deletions test/test_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,54 @@ def test_pragma(self):
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual([], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd=CATKIN_PACKAGE]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual([], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd!=PROJECT]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual([], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd!=CATKIN_PACKAGE]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual(["CMD_CASE"], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd~(?i)catkin_package]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual([], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd!~(?i)catkin_package]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual(["CMD_CASE"], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case[cmd=PROJECT]
project(mock)
find_package(catkin REQUIRED)
CATKIN_PACKAGE()
""", checks=cc.all)
self.assertEqual(["CMD_CASE"], result)
result = mock_lint(env, pkg,
"""
#catkin_lint: ignore cmd_case
Expand Down

0 comments on commit e310ed9

Please sign in to comment.