Class: SasLinter::Rules::MalformedLabelStatement

Inherits:
SasLinter::Rule show all
Defined in:
lib/sas_linter/rules/malformed_label_statement.rb

Overview

Flag ‘label` statements where the `=` between the variable name and the string literal is missing.

Motivating bug: ‘label aHSDELIRIUM ’Delirium Screener’;‘ in SUITE9_HS_DELIRIUM_SCREENER_2014-04-15.TXT — SAS rejects this with “ERROR 22-322: Syntax error, expecting one of the following:

, ?“, and the label is silently never attached. The treatment

variant of the same algorithm shipped the same typo, and several other interRAI sources have shipped it over time.

Detection: every ‘label` statement is a `KW_LABEL` keyword followed by one or more `IDENT ’=‘ STRING_LITERAL` triples separated by whitespace, terminated by `;`. We walk each label statement and, for each IDENT inside it, require the next default-channel token to be `ASSIGN` (`=`). If instead the next token is a STRING_LITERAL, the `=` was dropped.

Constant Summary collapse

TT =
SasLexer::Lexer::TokenType

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from SasLinter::Rule

all, #autofix?, description, fetch, from_config, inherited, #initialize, register, registry, rule_id, severity

Constructor Details

This class inherits a constructor from SasLinter::Rule

Class Method Details

.supports_autofix?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/sas_linter/rules/malformed_label_statement.rb', line 31

def self.supports_autofix?
  true
end

Instance Method Details

#autofix(source) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/sas_linter/rules/malformed_label_statement.rb', line 49

def autofix(source)
  return source if source.nil? || source.empty?

  lexer = SasLexer::Lexer.new
  begin
    all_tokens = lexer.tokenize(source)
  ensure
    lexer.free
  end
  tokens = all_tokens.reject do |t|
    t[:channel] == SasLexer::Lexer::TokenChannel::HIDDEN ||
      t[:channel] == SasLexer::Lexer::TokenChannel::COMMENT
  end

  source_lines = source.split("\n", -1)
  # Collect (line_idx, col_after_ident) for each malformed label,
  # then apply edits right-to-left within each line so earlier
  # column offsets stay valid.
   = Hash.new { |h, k| h[k] = [] }

  each_label_violation(tokens) do |ident_t, _string_t|
    [ident_t[:start_line] - 1] << ident_t[:end_column]
  end

  .each do |line_idx, cols|
    line = source_lines[line_idx]
    next if line.nil?

    # Right-to-left so earlier insertions don't shift later columns.
    cols.sort.reverse.each do |col|
      # Insert ` =` immediately after the IDENT (consuming the
      # following space if there is one, preserving alignment).
      replacement =
        if col < line.length && line[col] == " "
          # `aHSDELIRIUM 'Delirium Screener'` → `aHSDELIRIUM = 'Delirium Screener'`.
          # Replace the single space with ` = ` (one space before, one after).
          " = #{line[(col + 1)..]}"
        else
          " = #{line[col..]}"
        end
      line = "#{line[0...col]}#{replacement}"
    end
    source_lines[line_idx] = line
  end

  source_lines.join("\n")
end

#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/sas_linter/rules/malformed_label_statement.rb', line 35

def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument
  findings = []
  each_label_violation(tokens) do |ident_t, string_t|
    findings << finding(
      line: ident_t[:start_line],
      column: ident_t[:start_column] + 1,
      message: "`label #{ident_t[:text]} #{shorten(string_t[:text])}` is missing the `=` " \
               "between the variable name and the label string.",
      path: path
    )
  end
  findings
end