diff options
Diffstat (limited to 'src/debputy/lsp/lsp_debian_changelog.py')
-rw-r--r-- | src/debputy/lsp/lsp_debian_changelog.py | 124 |
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 |