Class: SasLinter::Rules::InconsistentVariableCase
- Inherits:
-
SasLinter::Rule
- Object
- SasLinter::Rule
- SasLinter::Rules::InconsistentVariableCase
- Defined in:
- lib/sas_linter/rules/inconsistent_variable_case.rb
Overview
Flag identifiers that are spelled with inconsistent letter case across the file. SAS resolves variable references case-insensitively, so ‘myVar` and `MyVar` end up bound to the same column — but mixing the two within one program is sloppy and makes the source harder to grep, diff, and read.
The most-used spelling wins; every other casing is reported (and rewritten when autofix is on). Ties resolve to the first occurrence so the canonical form is reading-order deterministic.
Skipped on purpose:
* identifiers immediately followed by `.` (format references like
`agecat.`, library references like `work.foo`);
* identifiers immediately preceded by `.` (the column half of
`lib.member` / `dataset.col`) — those name a column in another
dataset, not a variable in the current step;
* `value` / `invalue` / `picture` themselves and the format name
directly following them — these are proc-format definitions,
not variable references. We match locally rather than tracking
a `proc format ... run;` block because real-world SAS files
meant to be `%include`d into a caller's data step often omit
the terminating `run;`, so a state machine would never close.
Constant Summary collapse
- TT =
SasLexer::Lexer::TokenType
- FORMAT_DEF_KEYWORDS =
Identifiers that introduce a format / informat / picture definition in a ‘proc format` step. The lexer types these as plain IDENTIFIERs (not keywords), so we recognize them by text.
%w[value invalue picture].freeze
Class Method Summary collapse
Instance Method Summary collapse
- #autofix(source) ⇒ Object
-
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument.
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
43 44 45 |
# File 'lib/sas_linter/rules/inconsistent_variable_case.rb', line 43 def self.supports_autofix? true end |
Instance Method Details
#autofix(source) ⇒ Object
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 |
# File 'lib/sas_linter/rules/inconsistent_variable_case.rb', line 61 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 edits = [] each_inconsistent_use(tokens) do |token, canonical| edits << [token[:start], token[:end], canonical] end # Apply right-to-left so earlier offsets stay valid. out = source.dup edits.sort_by! { |start, _, _| -start } edits.each { |start, finish, repl| out[start...finish] = repl } out end |
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
rubocop:disable Lint/UnusedMethodArgument
47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/sas_linter/rules/inconsistent_variable_case.rb', line 47 def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument findings = [] each_inconsistent_use(tokens) do |token, canonical| findings << finding( line: token[:start_line], column: token[:start_column] + 1, message: "variable `#{token[:text]}` is spelled `#{canonical}` " \ "elsewhere in this file — pick one case and stick with it.", path: path ) end findings end |