Class: RemLint::Rules::UnknownSubstitutionSequence

Inherits:
RemLint::Rule
  • Object
show all
Defined in:
lib/remlint/rules/unknown_substitution_sequence.rb

Overview

% sequences Remind does not substitute.

Narrower than it first looks, and the narrowing is worth stating. The set in dosubst.c is closed but wide: the switch is on UPPER(c), and every one of the twenty-six letters and all ten digits is a case. So there is no %z typo to catch -- %z is the seconds-since-epoch sequence. What is left to report is punctuation, and the argument forms that were opened and never closed.

It is worth reporting because the failure is invisible. The default: branch emits the character and drops the percent: %~ prints ~, with no error, no warning, and nothing to distinguish it from text somebody meant to type.

Four sequences take an argument rather than a single character, and all four are recognised here so their contents are not mistaken for the sequence itself:

%<Header>   an INFO header      (silently empty when absent)
%{name}     calls subst_name    (warns only at warning level 05.00.03)
%(text)     a translated string
%*x         the alternate-mode modifier, then a real sequence

Leaving one of the first three unterminated is a warning from Remind at run time; reporting it here says the same thing without running the file.

A % at the very end of a body is not an error. It suppresses the newline Remind would otherwise append -- dosubst("hello") is six characters and dosubst("hello%") is five (remind.1: "if str does not end with %, a newline character will be added"). Remind's own tests/test.rem ends 160 bodies that way.

Only reminder bodies are checked. Elsewhere on a line a % is an ordinary character -- BANNER % is the idiom for no banner at all.

Constant Summary collapse

LETTERS =

From the switch (UPPER(c)) in src/dosubst.c. Every letter and every digit is a case; the punctuation is the part that is actually a set.

("A".."Z").to_a.freeze
DIGITS =
("0".."9").to_a.freeze
PUNCTUATION =
%w[! ? @ # : _ "].freeze
LITERAL_PERCENT =

%% has no case of its own, but the default branch emits the second % -- so it is the working way to write a literal percent, and reporting it would be reporting the fix.

"%"
SIMPLE =
(LETTERS + DIGITS + PUNCTUATION + [LITERAL_PERCENT]).freeze
TRAILING =

A trailing % suppresses the newline; the regex reports it as an empty body, because there is no character after the percent to report.

""
UNTERMINATED =

An opener with no closer before end of body. Remind warns about each of these at run time ("Unterminated %<...> substitution sequence").

{ "<" => ">", "{" => "}", "(" => ")" }.freeze
SEQUENCE =

The whole sequence, in every form, so the scan advances past an argument rather than stopping inside it.

/%(?<modifier>\*?)(?<body><[^>]*>|\{[^}]*\}|\([^)]*\)|.|\z)/
ARGUMENT_FORMS =
/\A[<{(]/

Constants inherited from RemLint::Rule

RemLint::Rule::REGISTRY

Instance Attribute Summary

Attributes inherited from RemLint::Rule

#config, #document, #offenses

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from RemLint::Rule

all, enabled_by_default?, find, inherited, #initialize, rule_name, #rule_name, #run

Constructor Details

This class inherits a constructor from RemLint::Rule

Class Method Details

.default_severityObject



69
70
71
# File 'lib/remlint/rules/unknown_substitution_sequence.rb', line 69

def self.default_severity
  "warning"
end

.descriptionObject



73
74
75
# File 'lib/remlint/rules/unknown_substitution_sequence.rb', line 73

def self.description
  "A % sequence in a reminder body that Remind does not substitute."
end

Instance Method Details

#checkObject



77
78
79
80
81
82
83
84
85
# File 'lib/remlint/rules/unknown_substitution_sequence.rb', line 77

def check
  document.code_commands.each do |command|
    body = body_of(command)

    if body
      report(command, body.fetch(:text), body.fetch(:offset))
    end
  end
end