sas-linter 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b12c05da17de3409a5eb163f3e3325cb7e2e24770d3de42e01efb786fc796d60
4
- data.tar.gz: b14b81be912177959a039484c70ea7bf71f209910cf995822d8083c8d6ea2897
3
+ metadata.gz: 688281e326c33b3af52864a9d25c85f00967a2c4f8ac9233e57cf52a83407b18
4
+ data.tar.gz: a9f331a62f73a5bdb7b637bbd62783f3969b169746e7035adf687afc3030ceb4
5
5
  SHA512:
6
- metadata.gz: 5fa28c6588e96bdd973197577e0e3fb1aa689fd641455ed10225f0354e0243e41fdb1e682b20d0de02d2a443b30ae9d263f419a31d42ea12763d91320bef7078
7
- data.tar.gz: aa8eb2ffe598911984fd57acff5ea350136865ccc64da7d7003b23033840013ac5c544a9bfd56d7f57bcb67c1f01de5af74c5b32f59d87300eac3525ae9e9bfe
6
+ metadata.gz: d952062a88c0bca6c33ef3f3e92c6cfeeec3f2142053e47c71dde3c04202ba0209b4672cad326161d62d05df90fb0940665cd0f65b73a62412d97b0ff6c8a1da
7
+ data.tar.gz: 7c41bac13b275a4dc9fc8bbf374339595b2b185fb6058e3ea27b412b18124a44bdb5b901eec7bdc9c0d7db3d9239ea0d56a30e6896ad88f0b9386dc74c2b1687
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../sas_linter"
4
+ require "sas_lexer"
5
+
6
+ class SasLinter
7
+ module Rules
8
+ # Flag `label` statements where the `=` between the variable name
9
+ # and the string literal is missing.
10
+ #
11
+ # Motivating bug: `label aHSDELIRIUM 'Delirium Screener';` in
12
+ # SUITE9_HS_DELIRIUM_SCREENER_2014-04-15.TXT — SAS rejects this
13
+ # with "ERROR 22-322: Syntax error, expecting one of the following:
14
+ # =, ?", and the label is silently never attached. The treatment
15
+ # variant of the same algorithm shipped the same typo, and several
16
+ # other interRAI sources have shipped it over time.
17
+ #
18
+ # Detection: every `label` statement is a `KW_LABEL` keyword
19
+ # followed by one or more `IDENT '=' STRING_LITERAL` triples
20
+ # separated by whitespace, terminated by `;`. We walk each label
21
+ # statement and, for each IDENT inside it, require the next
22
+ # default-channel token to be `ASSIGN` (`=`). If instead the next
23
+ # token is a STRING_LITERAL, the `=` was dropped.
24
+ class MalformedLabelStatement < Rule
25
+ rule_id :malformed_label_statement
26
+ description "`label` statement missing `=` between variable and string literal."
27
+ severity :warning
28
+
29
+ TT = SasLexer::Lexer::TokenType
30
+
31
+ def self.supports_autofix?
32
+ true
33
+ end
34
+
35
+ def check(tokens, path:, all_tokens: nil, source: nil) # rubocop:disable Lint/UnusedMethodArgument
36
+ findings = []
37
+ each_label_violation(tokens) do |ident_t, string_t|
38
+ findings << finding(
39
+ line: ident_t[:start_line],
40
+ column: ident_t[:start_column] + 1,
41
+ message: "`label #{ident_t[:text]} #{shorten(string_t[:text])}` is missing the `=` " \
42
+ "between the variable name and the label string.",
43
+ path: path
44
+ )
45
+ end
46
+ findings
47
+ end
48
+
49
+ def autofix(source)
50
+ return source if source.nil? || source.empty?
51
+
52
+ lexer = SasLexer::Lexer.new
53
+ begin
54
+ all_tokens = lexer.tokenize(source)
55
+ ensure
56
+ lexer.free
57
+ end
58
+ tokens = all_tokens.reject do |t|
59
+ t[:channel] == SasLexer::Lexer::TokenChannel::HIDDEN ||
60
+ t[:channel] == SasLexer::Lexer::TokenChannel::COMMENT
61
+ end
62
+
63
+ source_lines = source.split("\n", -1)
64
+ # Collect (line_idx, col_after_ident) for each malformed label,
65
+ # then apply edits right-to-left within each line so earlier
66
+ # column offsets stay valid.
67
+ edits_by_line = Hash.new { |h, k| h[k] = [] }
68
+
69
+ each_label_violation(tokens) do |ident_t, _string_t|
70
+ edits_by_line[ident_t[:start_line] - 1] << ident_t[:end_column]
71
+ end
72
+
73
+ edits_by_line.each do |line_idx, cols|
74
+ line = source_lines[line_idx]
75
+ next if line.nil?
76
+
77
+ # Right-to-left so earlier insertions don't shift later columns.
78
+ cols.sort.reverse.each do |col|
79
+ # Insert ` =` immediately after the IDENT (consuming the
80
+ # following space if there is one, preserving alignment).
81
+ replacement =
82
+ if col < line.length && line[col] == " "
83
+ # `aHSDELIRIUM 'Delirium Screener'` → `aHSDELIRIUM = 'Delirium Screener'`.
84
+ # Replace the single space with ` = ` (one space before, one after).
85
+ " = #{line[(col + 1)..]}"
86
+ else
87
+ " = #{line[col..]}"
88
+ end
89
+ line = "#{line[0...col]}#{replacement}"
90
+ end
91
+ source_lines[line_idx] = line
92
+ end
93
+
94
+ source_lines.join("\n")
95
+ end
96
+
97
+ private
98
+
99
+ def each_label_violation(tokens)
100
+ tokens.each_with_index do |t, i|
101
+ next unless t[:type] == TT::KW_LABEL
102
+
103
+ # Walk forward through the label statement until SEMI.
104
+ j = i + 1
105
+ while j < tokens.length && tokens[j][:type] != TT::SEMI
106
+ cur = tokens[j]
107
+ if cur[:type] == TT::IDENTIFIER
108
+ nxt = tokens[j + 1]
109
+ if nxt && nxt[:type] == TT::STRING_LITERAL
110
+ yield cur, nxt
111
+ j += 2
112
+ next
113
+ end
114
+ end
115
+ j += 1
116
+ end
117
+ end
118
+ end
119
+
120
+ def shorten(text)
121
+ return text if text.nil? || text.length <= 40
122
+
123
+ "#{text[0, 37]}..."
124
+ end
125
+ end
126
+ end
127
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class SasLinter
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
data/lib/sas_linter.rb CHANGED
@@ -307,6 +307,7 @@ require_relative "sas_linter/rules/line_endings"
307
307
  require_relative "sas_linter/rules/encoding_issues"
308
308
  require_relative "sas_linter/rules/malformed_if_condition"
309
309
  require_relative "sas_linter/rules/missing_assignment_semicolon"
310
+ require_relative "sas_linter/rules/malformed_label_statement"
310
311
  require_relative "sas_linter/rules/variable_value_out_of_known_range"
311
312
  require_relative "sas_linter/rules/invalid_numeric_literal"
312
313
  require_relative "sas_linter/rules/inconsistent_variable_case"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sas-linter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig McNamara
@@ -69,6 +69,7 @@ files:
69
69
  - lib/sas_linter/rules/invalid_numeric_literal.rb
70
70
  - lib/sas_linter/rules/line_endings.rb
71
71
  - lib/sas_linter/rules/malformed_if_condition.rb
72
+ - lib/sas_linter/rules/malformed_label_statement.rb
72
73
  - lib/sas_linter/rules/missing_assignment_semicolon.rb
73
74
  - lib/sas_linter/rules/source_headers.rb
74
75
  - lib/sas_linter/rules/tab_expansion.rb