Class: SasLinter::Rules::UnreachableInnerBranchValue
- Inherits:
-
SasLinter::Rule
- Object
- SasLinter::Rule
- SasLinter::Rules::UnreachableInnerBranchValue
- Defined in:
- lib/sas_linter/rules/unreachable_inner_branch_value.rb
Overview
Flag inner branches whose comparison values are excluded by an
enclosing if VAR in (...) then do; ... end; guard.
Motivating shape: an outer guard
if RANK in (0,1,2,3,4,5,6,8) then do; omits 7, while an inner
if RANK in (5,6,7,8) then cOut = 2; lists 7. Value 7 falls
through the outer guard, so the inner branch can never fire for it
and cOut silently stays missing.
Detection: outer guard pushes a allowed_set frame; inner
if VAR in (...), if VAR = N, or if VAR eq N references inside
the same DO block are checked against that set. Values absent from
the outer set produce a finding.
Constant Summary collapse
- TT =
SasLexer::Lexer::TokenType
Instance Attribute Summary
Attributes inherited from SasLinter::Rule
Instance Method Summary collapse
-
#check(tokens, path:, all_tokens: nil, source: nil) ⇒ Object
Outer guard pattern: KW_IF IDENT KW_IN LPAREN <lits...> RPAREN KW_THEN KW_DO SEMI Inner check patterns: KW_IF IDENT(V) KW_IN LPAREN <lits...> RPAREN KW_IF IDENT(V) KW_EQ
KW_IF IDENT(V) ASSIGN (SAS uses =as comparison in IF).
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
Outer guard pattern: KW_IF IDENT KW_IN LPAREN <lits...> RPAREN KW_THEN KW_DO SEMI Inner check patterns:
KW_IF IDENT(V) KW_IN LPAREN <lits...> RPAREN
KW_IF IDENT(V) KW_EQ <lit>
KW_IF IDENT(V) ASSIGN <lit> (SAS uses `=` as comparison in IF)
35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 |
# File 'lib/sas_linter/rules/unreachable_inner_branch_value.rb', line 35 def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument findings = [] guard_stack = [] # array of {var:, allowed:, depth:} do_depth = 0 i = 0 while i < tokens.length tok = tokens[i] if tok[:type] == TT::KW_IF consumed, frame, inner_findings = analyze_if(tokens, i, do_depth, guard_stack, path) findings.concat(inner_findings) if frame guard_stack.push(frame) do_depth += 1 end i += consumed next end if tok[:type] == TT::KW_DO # bare `do;` (no IF prefix), or `do i = 1 to N;` — both increment depth do_depth += 1 i += 1 next end if tok[:type] == TT::KW_END do_depth -= 1 if do_depth > 0 guard_stack.pop while guard_stack.last && guard_stack.last[:depth] > do_depth i += 1 next end i += 1 end findings end |