import textwrap from typing import List, Optional import pytest from debputy.lsp.lsp_debian_control import _lint_debian_control from debputy.packages import DctrlParser from debputy.plugin.api.feature_set import PluginProvidedFeatureSet from lint_tests.lint_tutil import ( group_diagnostics_by_severity, requires_levenshtein, LintWrapper, ) try: from lsprotocol.types import Diagnostic, DiagnosticSeverity except ImportError: pass STANDARDS_VERSION = "4.7.0" class DctrlLintWrapper(LintWrapper): def __call__(self, lines: List[str]) -> Optional[List["Diagnostic"]]: try: self.dctrl_lines = lines return super().__call__(lines) finally: self.dctrl_lines = None @pytest.fixture def line_linter( debputy_plugin_feature_set: PluginProvidedFeatureSet, lint_dctrl_parser: DctrlParser, ) -> LintWrapper: return DctrlLintWrapper( "/nowhere/debian/control", _lint_debian_control, debputy_plugin_feature_set, lint_dctrl_parser, ) def test_dctrl_lint(line_linter: LintWrapper) -> None: lines = textwrap.dedent( """\ Source: foo Some-Other-Field: bar Build-Depends: debhelper-compat (= 13) Package: foo Architecture: all # Unknown section Section: base """ ).splitlines(keepends=True) diagnostics = line_linter(lines) by_severity = group_diagnostics_by_severity(diagnostics) # This example triggers errors and warnings, but no hint of info assert DiagnosticSeverity.Error in by_severity assert DiagnosticSeverity.Warning in by_severity assert DiagnosticSeverity.Hint not in by_severity assert DiagnosticSeverity.Information not in by_severity errors = by_severity[DiagnosticSeverity.Error] print(errors) assert len(errors) == 3 first_error, second_error, third_error = errors msg = "Stanza is missing field Standards-Version" assert first_error.message == msg assert f"{first_error.range}" == "0:0-1:0" msg = "Stanza is missing field Maintainer" assert second_error.message == msg assert f"{second_error.range}" == "0:0-1:0" msg = "Stanza is missing field Priority" assert third_error.message == msg assert f"{third_error.range}" == "4:0-5:0" warnings = by_severity[DiagnosticSeverity.Warning] assert len(warnings) == 2 first_warn, second_warn = warnings msg = "Stanza is missing field Description" assert first_warn.message == msg assert f"{first_warn.range}" == "4:0-5:0" msg = 'The value "base" is not supported in Section.' assert second_warn.message == msg assert f"{second_warn.range}" == "7:9-7:13" @requires_levenshtein def test_dctrl_lint_typos(line_linter: LintWrapper) -> None: lines = textwrap.dedent( f"""\ Source: foo Standards-Version: {STANDARDS_VERSION} Priority: optional Section: devel Maintainer: Jane Developer # Typo Build-Dpends: debhelper-compat (= 13) Package: foo Architecture: all Description: Some very interesting synopsis A very interesting description that spans multiple lines . Just so be clear, this is for a test. """ ).splitlines(keepends=True) diagnostics = line_linter(lines) print(diagnostics) assert diagnostics and len(diagnostics) == 1 diag = diagnostics[0] msg = 'The "Build-Dpends" looks like a typo of "Build-Depends".' assert diag.message == msg assert diag.severity == DiagnosticSeverity.Warning assert f"{diag.range}" == "6:0-6:12" @requires_levenshtein def test_dctrl_lint_mx_value_with_typo(line_linter: LintWrapper) -> None: lines = textwrap.dedent( f"""\ Source: foo Standards-Version: {STANDARDS_VERSION} Priority: optional Section: devel Maintainer: Jane Developer Build-Depends: debhelper-compat (= 13) Package: foo # Typo of `all` Architecture: linux-any alle Description: Some very interesting synopsis A very interesting description that spans multiple lines . Just so be clear, this is for a test. """ ).splitlines(keepends=True) diagnostics = line_linter(lines) print(diagnostics) assert diagnostics is not None assert len(diagnostics) == 2 by_severity = group_diagnostics_by_severity(diagnostics) assert DiagnosticSeverity.Error in by_severity assert DiagnosticSeverity.Warning in by_severity typo_diag = by_severity[DiagnosticSeverity.Warning][0] mx_diag = by_severity[DiagnosticSeverity.Error][0] mx_msg = 'The value "all" cannot be used with other values.' typo_msg = 'It is possible that the value is a typo of "all".' assert mx_diag.message == mx_msg assert typo_diag.message == typo_msg assert f"{mx_diag.range}" == "9:24-9:28" assert f"{typo_diag.range}" == "9:24-9:28" def test_dctrl_lint_mx_value(line_linter: LintWrapper) -> None: lines = textwrap.dedent( f"""\ Source: foo Standards-Version: {STANDARDS_VERSION} Priority: optional Section: devel Maintainer: Jane Developer Build-Depends: debhelper-compat (= 13) Package: foo Architecture: all linux-any Description: Some very interesting synopsis A very interesting description that spans multiple lines . Just so be clear, this is for a test. """ ).splitlines(keepends=True) diagnostics = line_linter(lines) print(diagnostics) assert diagnostics and len(diagnostics) == 1 diag = diagnostics[0] msg = 'The value "all" cannot be used with other values.' assert diag.message == msg assert diag.severity == DiagnosticSeverity.Error assert f"{diag.range}" == "8:14-8:17" lines = textwrap.dedent( f"""\ Source: foo Standards-Version: {STANDARDS_VERSION} Priority: optional Section: devel Maintainer: Jane Developer Build-Depends: debhelper-compat (= 13) Package: foo Architecture: linux-any any Description: Some very interesting synopsis A very interesting description that spans multiple lines . Just so be clear, this is for a test. """ ).splitlines(keepends=True) diagnostics = line_linter(lines) print(diagnostics) assert diagnostics and len(diagnostics) == 1 diag = diagnostics[0] msg = 'The value "any" cannot be used with other values.' assert diag.message == msg assert diag.severity == DiagnosticSeverity.Error assert f"{diag.range}" == "8:24-8:27" def test_dctrl_lint_dup_sep(line_linter: LintWrapper) -> None: lines = textwrap.dedent( f"""\ Source: foo Section: devel Priority: optional Standards-Version: {STANDARDS_VERSION} Maintainer: Jane Developer Build-Depends: debhelper-compat (= 13) Package: foo Architecture: all Depends: foo, , bar Description: Some very interesting synopsis A very interesting description that spans multiple lines . Just so be clear, this is for a test. """ ).splitlines(keepends=True) diagnostics = line_linter(lines) print(diagnostics) assert diagnostics and len(diagnostics) == 1 error = diagnostics[0] msg = "Duplicate separator" assert error.message == msg assert f"{error.range}" == "10:1-10:2" assert error.severity == DiagnosticSeverity.Error