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 +4 -4
- data/lib/sas_linter/rules/malformed_label_statement.rb +127 -0
- data/lib/sas_linter/version.rb +1 -1
- data/lib/sas_linter.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 688281e326c33b3af52864a9d25c85f00967a2c4f8ac9233e57cf52a83407b18
|
|
4
|
+
data.tar.gz: a9f331a62f73a5bdb7b637bbd62783f3969b169746e7035adf687afc3030ceb4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/lib/sas_linter/version.rb
CHANGED
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.
|
|
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
|