summaryrefslogtreecommitdiffstats
path: root/src/debputy/lsp/lsp_debian_changelog.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/debputy/lsp/lsp_debian_changelog.py')
-rw-r--r--src/debputy/lsp/lsp_debian_changelog.py124
1 files changed, 124 insertions, 0 deletions
diff --git a/src/debputy/lsp/lsp_debian_changelog.py b/src/debputy/lsp/lsp_debian_changelog.py
index 3ec0b4d..77df145 100644
--- a/src/debputy/lsp/lsp_debian_changelog.py
+++ b/src/debputy/lsp/lsp_debian_changelog.py
@@ -1,4 +1,5 @@
import sys
+from email.utils import parsedate_to_datetime
from typing import (
Union,
List,
@@ -26,6 +27,7 @@ from lsprotocol.types import (
from debputy.lsp.lsp_features import lsp_diagnostics, lsp_standard_handler
from debputy.lsp.quickfixes import (
provide_standard_quickfixes_from_diagnostics,
+ propose_correct_text_quick_fix,
)
from debputy.lsp.spellchecking import spellcheck_line
from debputy.lsp.text_util import (
@@ -52,6 +54,17 @@ _LANGUAGE_IDS = [
"debchangelog",
]
+_WEEKDAYS_BY_IDX = [
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun",
+]
+_KNOWN_WEEK_DAYS = frozenset(_WEEKDAYS_BY_IDX)
+
DOCUMENT_VERSION_TABLE: Dict[str, int] = {}
@@ -106,6 +119,114 @@ def _diagnostics_debian_changelog(
yield from scanner
+def _check_footer_line(
+ line: str,
+ line_no: int,
+ lines: List[str],
+ position_codec: LintCapablePositionCodec,
+) -> Iterator[Diagnostic]:
+ try:
+ end_email_idx = line.rindex("> ")
+ except ValueError:
+ # Syntax error; flag later
+ return
+ line_len = len(line)
+ start_date_idx = end_email_idx + 3
+ # 3 characters for the day name (Mon), then a comma plus a space followed by the
+ # actual date. The 6 characters limit is a gross under estimation of the real
+ # size.
+ if line_len < start_date_idx + 6:
+ range_server_units = Range(
+ Position(
+ line_no,
+ start_date_idx,
+ ),
+ Position(
+ line_no,
+ line_len,
+ ),
+ )
+ yield Diagnostic(
+ position_codec.range_to_client_units(lines, range_server_units),
+ "Expected a date in RFC822 format (Tue, 12 Mar 2024 12:34:56 +0000)",
+ severity=DiagnosticSeverity.Error,
+ source="debputy",
+ )
+ return
+ day_name_range_server_units = Range(
+ Position(
+ line_no,
+ start_date_idx,
+ ),
+ Position(
+ line_no,
+ start_date_idx + 3,
+ ),
+ )
+ day_name = line[start_date_idx : start_date_idx + 3]
+ if day_name not in _KNOWN_WEEK_DAYS:
+ yield Diagnostic(
+ position_codec.range_to_client_units(lines, day_name_range_server_units),
+ "Expected a three letter date here (Mon, Tue, ..., Sun).",
+ severity=DiagnosticSeverity.Error,
+ source="debputy",
+ )
+ return
+
+ date_str = line[start_date_idx + 5 :]
+
+ if line[start_date_idx + 3 : start_date_idx + 5] != ", ":
+ sep = line[start_date_idx + 3 : start_date_idx + 5]
+ range_server_units = Range(
+ Position(
+ line_no,
+ start_date_idx + 3,
+ ),
+ Position(
+ line_no,
+ start_date_idx + 4,
+ ),
+ )
+ yield Diagnostic(
+ position_codec.range_to_client_units(lines, range_server_units),
+ f'Improper formatting of date. Expected ", " here, not "{sep}"',
+ severity=DiagnosticSeverity.Error,
+ source="debputy",
+ )
+ return
+
+ try:
+ # FIXME: this parser is too forgiving (it ignores trailing garbage)
+ date = parsedate_to_datetime(date_str)
+ except ValueError as e:
+ range_server_units = Range(
+ Position(
+ line_no,
+ start_date_idx + 5,
+ ),
+ Position(
+ line_no,
+ line_len,
+ ),
+ )
+ yield Diagnostic(
+ position_codec.range_to_client_units(lines, range_server_units),
+ f"Unable to the date as a valid RFC822 date: {e.args[0]}",
+ severity=DiagnosticSeverity.Error,
+ source="debputy",
+ )
+ return
+ expected_week_day = _WEEKDAYS_BY_IDX[date.weekday()]
+ if expected_week_day != day_name:
+ yield Diagnostic(
+ position_codec.range_to_client_units(lines, day_name_range_server_units),
+ f"The date was a {expected_week_day}day.",
+ severity=DiagnosticSeverity.Warning,
+ source="debputy",
+ data=[propose_correct_text_quick_fix(expected_week_day)],
+ )
+
+
def _scan_debian_changelog_for_diagnostics(
lines: List[str],
position_codec: LintCapablePositionCodec,
@@ -123,6 +244,9 @@ def _scan_debian_changelog_for_diagnostics(
line = line.rstrip()
if not line:
continue
+ if line.startswith(" --"):
+ diagnostics.extend(_check_footer_line(line, line_no, lines, position_codec))
+ continue
if not line.startswith(" "):
continue
# minus 1 for newline