Source code for statick_tool.plugins.tool.cpplint
"""Apply Cpplint tool and gather results."""
import logging
import os
import re
import subprocess
from typing import Match, Optional, Pattern
from statick_tool.issue import Issue
from statick_tool.package import Package
from statick_tool.tool_plugin import ToolPlugin
[docs]
class CpplintToolPlugin(ToolPlugin):
"""Apply Cpplint tool and gather results."""
[docs]
def get_name(self) -> str:
"""Get name of tool.
Returns:
Name of the tool.
"""
return "cpplint"
[docs]
def get_binary( # pylint: disable=unused-argument
self, level: Optional[str] = None, package: Optional[Package] = None
) -> str:
"""Return the name of the tool binary.
Args:
level: The level of the scan.
package: The package to scan.
Returns:
The name of the tool binary.
"""
binary = self.get_name()
if package is not None and "cpplint" in package:
binary = package["cpplint"]
return binary
[docs]
def scan(self, package: Package, level: str) -> Optional[list[Issue]]:
"""Run tool and gather output.
Args:
package: The package to scan.
level: The level of the scan.
Returns:
A list of issues found by the tool.
"""
if "make_targets" not in package and "headers" not in package:
return []
if not package["make_targets"] and not package["headers"]:
return []
if "cpplint" not in package:
logging.warning(" cpplint not found!")
return None
flags: list[str] = []
flags += self.get_user_flags(level)
cpplint = self.get_binary(package=package)
files: list[str] = []
if "make_targets" in package:
for target in package["make_targets"]:
files += target["src"]
try:
output = subprocess.check_output(
[cpplint] + flags + files,
stderr=subprocess.STDOUT,
universal_newlines=True,
)
except subprocess.CalledProcessError as ex:
output = ex.output
if ex.returncode != 1:
logging.warning("cpplint failed! Returncode = %d", ex.returncode)
logging.warning("%s exception: %s", self.get_name(), ex.output)
return None
except OSError as ex:
logging.warning("Couldn't find cpplint executable! (%s)", ex)
return None
logging.debug("%s", output)
if self.plugin_context and self.plugin_context.args.output_directory:
with open(self.get_name() + ".log", "w", encoding="utf8") as fid:
fid.write(output)
issues: list[Issue] = self.parse_tool_output(output)
return issues
[docs]
@classmethod
def check_for_exceptions(cls, match: Match[str]) -> bool:
"""Manual exceptions.
Args:
match: The regex match object.
Returns:
True if the match is an exception, False otherwise.
"""
if (
match.group(1).endswith(".cpp") or match.group(1).endswith(".cc")
) and match.group(4) == "build/namespaces":
# allow using namespace inside source files
return True
if match.group(4) == "build/namespaces" and "unnamed" in match.group(3):
# ignore anonymous namespace warning
return True
if (
"cfg/cpp" in match.group(1)
and match.group(1).endswith("Config.h")
and match.group(4) == "build/storage_class"
):
# ignoring issue in auto-generated ROS code
return True
return False
[docs]
def parse_tool_output(self, output: str) -> list[Issue]:
"""Parse tool output and report issues.
Args:
output: The output from the tool.
Returns:
A list of issues found by the tool.
"""
lint_re = r"(.+):(\d+):\s(.+)\s\[(.+)\]\s\[(\d+)\]"
parse: Pattern[str] = re.compile(lint_re)
issues: list[Issue] = []
for line in output.splitlines():
match: Optional[Match[str]] = parse.match(line)
if match and not self.check_for_exceptions(match):
norm_path = os.path.normpath(match.group(1))
issues.append(
Issue(
norm_path,
int(match.group(2)),
self.get_name(),
match.group(4),
int(match.group(5)),
match.group(3),
None,
)
)
return issues