Class: SasLinter::Rules::FormatForUnknownVariable

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

Overview

Flag ‘format` / `informat` / `attrib … format=` statements that name a variable referenced nowhere else in the file. Almost always a typo (`attrib totalscore format=flagx.;` when every other use is `total_score`). SAS itself silently binds the format to a phantom column and runs; downstream tooling that resolves variable references (e.g. sas-ruby) refuses to compile such a file.

The rule is conservative: a file that pulls variables in from an external source — ‘set`, `merge`, `update`, `infile`, `input` — is skipped entirely, since a format target may legitimately name a column the linter can’t see.

Constant Summary collapse

TT =
SasLexer::Lexer::TokenType
FORMAT_KIND_BY_TYPE =
{
  TT::KW_FORMAT => :format,
  TT::KW_INFORMAT => :informat,
  TT::KW_ATTRIB => :attrib
}.freeze
EXTERNAL_INPUT_TYPES =
[
  TT::KW_SET, TT::KW_MERGE, TT::KW_UPDATE, TT::KW_INFILE, TT::KW_INPUT
].freeze
DECLARATION_TYPES =

Statements that name variables for declaration only — the names they reference don’t count as “real uses” because if a variable only appears in declaration statements it’s still dead code. Keyword-typed openers:

[
  TT::KW_FORMAT, TT::KW_INFORMAT, TT::KW_ATTRIB,
  TT::KW_LABEL, TT::KW_LENGTH, TT::KW_KEEP, TT::KW_DROP, TT::KW_ARRAY
].freeze
DECLARATION_TEXT =

IDENTIFIER-typed openers (the lexer doesn’t keyword these):

`retain` is a data-step declaration;
`value` / `invalue` / `picture` introduce a `proc format` body.
%w[retain value invalue picture].freeze

Instance Attribute Summary

Attributes inherited from SasLinter::Rule

#autofix

Instance Method Summary collapse

Methods inherited from SasLinter::Rule

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

Constructor Details

This class inherits a constructor from SasLinter::Rule

Instance Method Details

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

rubocop:disable Lint/UnusedMethodArgument



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
# File 'lib/sas_linter/rules/format_for_unknown_variable.rb', line 52

def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument
  external_input = false
  targets = []
  use_names = Set.new

  each_statement(tokens) do |stmt|
    opener = stmt[0]

    if EXTERNAL_INPUT_TYPES.include?(opener[:type])
      external_input = true
      next
    end

    kind = FORMAT_KIND_BY_TYPE[opener[:type]]
    if kind
      collect_targets(stmt, kind, targets)
      next
    end

    next if declaration_statement?(opener)

    collect_uses(stmt, use_names)
  end

  return [] if external_input

  targets.filter_map do |t, kind|
    next if use_names.include?(t[:text].downcase)

    finding(
      line: t[:start_line],
      column: t[:start_column] + 1,
      message: "`#{kind}` assigns a format to `#{t[:text]}` but " \
               "that variable is not referenced anywhere else in " \
               "this file — likely a typo.",
      path: path
    )
  end
end