短毛熊

欢迎。本教程旨在向您展示如何使用 @linter (ref )装饰者,以便在您的熊中集成绒布。

备注

如果您计划创建一只无需包装工具即可执行静电代码分析的熊,请参阅 this link instead .

本教程将带领您完成编写本地林特熊的过程。如果您想要为一个工具编写全局Linter Bear,该工具不会为每个文件运行一次,而是整个项目只运行一次,您仍然可以通过以下地址完成这些步骤,然后阅读全局Linter Bear的不同之处 global_bears .

这为什么有用呢?

许多编程语言已经实现了Linters,所以如果您的项目使用的语言还没有Linter Bear,您可能需要自己实现它。别担心,这很容易!

我们需要什么?

首先,我们需要要使用的Linter可执行文件。在本教程中,我们将构建PylintTutorialBear,因此我们需要Pylint,它是Python的通用Linter。你可以找到它 here 。因为它是一个python包,所以我们可以使用

$ pip3 install pylint

写“熊”

要编写LinterBear,我们需要创建一个与LinterBear基础设施接口的类,该基础设施是通过 @linter 装饰者。

from coalib.bearlib.abstractions.Linter import linter

@linter(executable='pylint')
class PylintTutorialBear:
    pass

如你所见 pylint 已经作为可执行文件名提供,将在要链接的文件上调用该可执行文件名。这是装饰者的强制参数。

为此,Linter类一次只能处理一个文件 pylint 或者每次都需要使用适当的参数调用外部工具。这是在里面做的 create_arguments

@linter(executable='pylint')
class PylintTutorialBear:
    @staticmethod
    def create_arguments(filename, file, config_file):
        pass

create_arguments 接受三个参数:

  • filename :要处理的文件的绝对路径。

  • file :要处理的文件内容,以行列表的形式给出(包括回车字符)。

  • config_file :要使用的配置文件的绝对路径。如果未使用配置文件,则此参数为 None 。配置文件的处理留给Bear的方法实现。

您可以使用这些参数来构造命令行参数。Linter期望您在这里返回一个参数序列。最好是元组。我们很快就会这么做的 PylintTutorialBear .

备注

create_arguments 不一定非得是静电的方法。在这种情况下,您还需要将 self 签名中的参数。的某些功能 @linter 仅在实例内部可用,如日志记录。

def create_arguments(self, filename, file, config_file):
    self.log("Hello world")

那么,我们需要提供哪些确切的命令行参数呢?这取决于Linter的输出格式。这个 @linter 装饰器能够处理不同的输出格式:

  • regex :它解析底层可执行文件生成的问题消息。

  • corrected :从工具提供的固定/更正文件自动生成结果。

  • unified-diff :这将从可执行文件提供的统一差异输出中自动生成结果。

在本教程中,我们将使用 regex 输出格式。但是在我们继续修改熊之前,我们需要弄清楚Pylint的输出到底是什么样子,以便我们可以相应地解析它。

在使用以下命令调用Pylint时,我们得到了一些有希望的输出

$ pylint --msg-template="L{line}C{column}: {msg_id} - {msg}" --reports=n

示例输出如下所示:

No config file found, using default configuration
************* Module coalib.bearlib.abstractions.Linter
L1C0: C0111 - Missing module docstring
L42C48: E1101 - Class 'Enum' has no 'reverse' member
L77C32: E1101 - Class 'Enum' has no 'reverse' member
L21C0: R0912 - Too many branches (16/12)
L121C28: W0613 - Unused argument 'filename'

这是我们可以用正则表达式轻松解析的东西。因此,让我们实现我们目前发现的所有内容:

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): (?P<message>.*)')
class PylintTutorialBear:
    @staticmethod
    def create_arguments(filename, file, config_file):
        return ('--msg-template="L{line}C{column}: {msg_id} - {msg}"',
                '--reports=n', filename)

如你所见, output_regex 参数由命名组组成。这些对于构建包含打印输出信息的有意义的结果非常重要。

有关指定组的确切列表,请参阅 @linter recognizes, see the API documentation .

请参考 Python3 re moduleDive into python 有关正则表达式的信息,请参阅。

让我们温习一下我们的 output_regex 要使用更多信息,请参阅以下内容:

@linter(...
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): '
                     r'(?P<message>(?P<origin>.\d+) - .*)'),
        ...)

现在,我们使用问题标识作为来源,因此可以通过代码中的忽略语句停用单个规则。

这个类已经功能齐全,并且允许解析由Pylint产生的问题!

使用Severities

Coala使用三种类型的严重度来对结果的重要性进行分类:

  • INFO

  • NORMAL

  • MAJOR

中定义的 coalib.results.RESULT_SEVERITY 。Pylint输出包含我们可以使用的严重性信息:

L1C0: C0111 - Missing module docstring

错误代码前的字母是严重性。为了利用严重性,我们需要在 output_regex 参数使用命名组 severity

@linter(...
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): (?P<message>'
                     r'(?P<origin>(?P<severity>[WFECRI])\d+) - .*)',
        ...)

所以我们想要接受这些字母所表示的严肃性 WFECRI 。为了使用此严重性值,我们首先必须提供一个映射,该映射获取匹配的严重度字母并将其映射到严重性值 coalib.results.RESULT_SEVERITY 所以科拉明白这一点。这可以通过 severity_map 参数 @linter

from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY

@linter(...
        severity_map={'W': RESULT_SEVERITY.NORMAL,
                      'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'C': RESULT_SEVERITY.NORMAL,
                      'R': RESULT_SEVERITY.NORMAL,
                      'I': RESULT_SEVERITY.INFO},
        ...)

我们可以这样测试我们的熊

$ coala --bear-dirs=. --bears=PylintTutorialBear --files=sample.py

备注

为了使上述命令起作用,我们的当前目录中应该有2个文件: PylintTutorialBear.py 以及我们的 sample.py 。命名是 very 在科拉很重要。科拉将通过它们的熊来寻找熊 文件名 并根据它们的 类的名称 .

通常情况下,不需要提供严重度图,因为Coala有一个默认的严重度图,可以识别许多表示严重度的常用词。有关支持的关键字,请查看API文档!

规格化行号或列号

Coala使用基于1的行和列约定,即第一行和第一列是1。但是,一些索引使用基于0的约定。例如, pylint 使用基于1的行约定和基于0的列约定。选项 normalize_line_numbersnormalize_column_numbers 可以帮助我们很容易地将林特的会议映射到科拉的会议。他们是 False 默认情况下。如果 normalize_line_numbersTrue ,行数将增加1。如果 normalize_column_numbersTrue ,列数将增加1。

注意 pylint 使用从0开始的列约定。我们需要将其映射到Coala的约定,如下所示:

@linter(...
        normalize_column_numbers = True,
        ...)

建议更正使用 correctedunified-diff 输出格式

这些输出格式使用起来非常简单,不需要您在熊内部进行进一步设置:

@linter(...
        output_format='corrected')

@linter(...
        output_format='unified-diff')

如果您的底层工具生成已更正的文件或更正的统一差异,该类将自动为所做的更改生成补丁,并相应地生成结果。

将设置添加到我们的熊

如果我们逃跑

$ pylint --help

我们可以看到有一个 --rcfile 选项,该选项允许我们为Pylint指定配置文件。让我们将该功能添加到我们的熊中。

import os

from coalib.bearlib.abstractions.Linter import linter
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): '
                     r'(?P<message>(?P<severity>[WFECRI]).*)',
        severity_map={'W': RESULT_SEVERITY.NORMAL,
                      'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'C': RESULT_SEVERITY.NORMAL,
                      'R': RESULT_SEVERITY.NORMAL,
                      'I': RESULT_SEVERITY.INFO})
class PylintTutorialBear:
    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_rcfile: str=os.devnull):
        return ('--msg-template="L{line}C{column}: {msg_id} - {msg}"',
                '--reports=n', '--rcfile=' + pylint_rcfile, filename)

只需将所需的参数添加到 create_arguments 签名就足够了,就像你对里面的其他熊所做的那样 run 好了!从Coafile中自动查询附加参数。我们还添加一些文档以及元数据属性:

@linter(...)
class PylintTutorialBear:
    """
    Lints your Python files!

    Checks for coding standards (like well-formed variable names), detects
    semantical errors (like true implementation of declared interfaces or
    membership via type inference), duplicated code.

    See http://pylint-messages.wikidot.com/all-messages for a list of all
    checks and error codes.
    """

    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_rcfile: str=os.devnull):
        """
        :param pylint_rcfile:
            The configuration file Pylint shall use.
        """
        ...

备注

参数的文档由Coala解析,它将被用作该特定设置的用户帮助。

成品熊

干得好,你走到这一步了!现在您应该已经构建了一个功能齐全的Python Linter Bear。如果您遵循本教程中的代码,它应该如下所示

import os

from coalib.bearlib.abstractions.Linter import linter
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): '
                     r'(?P<message>(?P<severity>[WFECRI]).*)',
        severity_map={'W': RESULT_SEVERITY.NORMAL,
                      'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'C': RESULT_SEVERITY.NORMAL,
                      'R': RESULT_SEVERITY.NORMAL,
                      'I': RESULT_SEVERITY.INFO})
class PylintTutorialBear:
    """
    Lints your Python files!

    Checks for coding standards (like well-formed variable names), detects
    semantical errors (like true implementation of declared interfaces or
    membership via type inference), duplicated code.

    See http://pylint-messages.wikidot.com/all-messages for a list of all
    checks and error codes.

    https://pylint.org/
    """

    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_rcfile: str=os.devnull):
        """
        :param pylint_rcfile:
            The configuration file Pylint shall use.
        """
        return ('--msg-template="L{line}C{column}: {msg_id} - {msg}"',
                '--reports=n', '--rcfile=' + pylint_rcfile, filename)

添加元数据属性

现在我们需要给我们的熊添加一些更有价值的信息。这有助于提供有关每个熊的更多信息,还有助于某些函数通过使用这些值来收集信息。我们的熊现在看起来像是:

import os

from coalib.bearlib.abstractions.Linter import linter
from dependency_management.requirements.PipRequirement import PipRequirement
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): '
                     r'(?P<message>(?P<severity>[WFECRI]).*)',
        severity_map={'W': RESULT_SEVERITY.NORMAL,
                      'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'C': RESULT_SEVERITY.NORMAL,
                      'R': RESULT_SEVERITY.NORMAL,
                      'I': RESULT_SEVERITY.INFO})
class PylintTutorialBear:
    """
    Lints your Python files!

    Checks for coding standards (like well-formed variable names), detects
    semantical errors (like true implementation of declared interfaces or
    membership via type inference), duplicated code.

    See http://pylint-messages.wikidot.com/all-messages for a list of all
    checks and error codes.

    https://pylint.org/
    """

    LANGUAGES = {'Python', 'Python 2', 'Python 3'}
    REQUIREMENTS = {PipRequirement('pylint', '1.*')}
    AUTHORS = {'The coala developers'}
    AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
    LICENSE = 'AGPL-3.0'
    CAN_DETECT = {'Unused Code', 'Formatting', 'Duplication', 'Security',
                  'Syntax'}
    SEE_MORE = 'https://pylint.org/'

    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_rcfile: str=os.devnull):
      """
      :param pylint_rcfile:
          The configuration file Pylint shall use.
      """
      return ('--msg-template="L{line}C{column}: {msg_id} - {msg}"',
              '--reports=n', '--rcfile=' + pylint_rcfile, filename)

备注

  • 对于语言命名,请始终考虑相应Wikipedia页面中使用的格式(例外情况:使用CPP,而不是CPP或C++)。

  • 如果该定义尚不存在,则在编写新的Bear时应添加语言定义。

  • 语言中使用的别名应该是Coala定义中的名称之一。

运行和测试我们的熊

通过运行

$ coala --bear-dirs=. --bears=PylintTutorialBear -B

我们可以看到,我们的Bear设置已正确记录。在启用熊的情况下使用Coala sample.py 我们跑

$ coala --bear-dirs=. --bears=PylintTutorialBear --files=sample.py

要使用我们的 pylint_rcfile 设置我们可以做的

$ coala --bear-dirs=. --bears=PylintTutorialBear \
> -S pylint_rcfile=my_rcfile --files=sample.py

现在您知道了如何编写一个Linter Bear,以及如何在您的项目中使用它。

祝贺你!

全球短毛熊

有些linting工具不是在文件级运行,即每个文件运行一次,而是在项目级运行。它们可能会检查目录结构的某些属性,或者只检查一个特定的文件,如 setup.py .

对于这些工具,我们需要一个 GlobalBear 我们还可以使用 @linter 给我们一个,通过传递参数 global_bear=True

from coalib.bearlib.abstractions.Linter import linter

@linter(executable='some_tool',
        global_bear=True,
        output_format='regex',
        output_regex=r'<filename>: <message>')
class SomeToolBear:
    @staticmethod
    def create_arguments(config_file):
        return []

这个 create_arguments 方法不接受 filenamefile 在这种情况下,因为没有文件上下文。您仍然可以让Coala知道在其中检测到问题的文件,方法是使用 filename 您的 output_regex 如果与缠绕的工具相关,请执行以下操作。

如前所述, create_arguments 不一定非得是静电的方法。在这种情况下,请记住预加 self 签名中的参数:

from coalib.bearlib.abstractions.Linter import linter

@linter(executable='some_tool',
        global_bear=True,
        output_format='regex',
        output_regex=r'<filename>: <message>')
class PythonTestBear:
    def create_arguments(self, config_file):
        return '--lint', self.file_dict.keys()

您可以使用以下命令访问完整的文件列表 self.file_dict ,它返回一个 {{filename: file contents}} 。请注意,当命令行的长度超过操作系统允许的长度,或者完整的文件列表大于操作系统允许的参数数量时,将文件的完整列表放在命令行上会导致中断。有关更多信息,请查看 here .

哪里可以找到更多的..。

如果您需要更多有关 @linter decorator, refer to the API documentation .