vinter 0.3.0 → 0.4.0

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: 7a7797098bc2eb7d9b046c4aa38d9d485fbee3016a709bda2f29ad0664b9d0de
4
- data.tar.gz: 807321a96f56c648922fc2b5923d8ccdfea6f6d8c8faa5fc3b844ddd4b6840ac
3
+ metadata.gz: e6f99b1ca16b7bbca35229a549ceed21f3cdf9b8bc86a3fedff9b978b18717de
4
+ data.tar.gz: 2aa4e1df5ae4fc08e78c06cf2107c7192099326b073bf45e45a9532b3fb5d0c3
5
5
  SHA512:
6
- metadata.gz: 4eb35205f6802d60d1c3070d7e8ff705d556367b4afc9c969cdfad3e560af3be646dcf13b4f7d59eaba0b0ed6cc61b130ce4cd952a529b5a4d5adfcae8339d6b
7
- data.tar.gz: '09ace9f8871da9f426f7efcf77e37962b466dcafbbc053725b2ffda42d51d47bd9c98a57dcaa191d51f7dface73ee2a8885fff5f30619c8c67636ff1e5218113'
6
+ metadata.gz: d739b01780f6ce12654b09b701f343e850bd2eb750e705ca608f793e7e9cfef26b226927157ff595dcbb3f62ddbbd4f530439074ef9fdbc9a2cfd075dd78a3da
7
+ data.tar.gz: 7ec3181349383a4b9667aab046f56099d25f830a07aaa6a6135b8640a25d59e85c2191b3ffbecd263afe703f1f17a986f40f769e458bab532e70f837f9c9a029
data/README.md CHANGED
@@ -2,13 +2,6 @@
2
2
 
3
3
  A Ruby gem that provides linting capabilities for Vim9 script files. This linter helps identify syntax errors and enforce best practices for for Vim9 script.
4
4
 
5
- ## Features
6
-
7
- - Lexical analysis of Vim9 script syntax
8
- - Parsing of Vim9 script constructs
9
- - Detection of common errors and code smells
10
- - Command-line interface for easy integration with editors
11
-
12
5
  ## Installation
13
6
 
14
7
  Install the gem:
@@ -17,11 +10,22 @@ Install the gem:
17
10
  gem install vinter
18
11
  ```
19
12
 
13
+ ## Configure
14
+ Vinter will read config files on the following priority order
15
+ - User config (`~/.vinter`)
16
+ - Project config (`path/to/proj/.vinter`)
17
+
18
+ ```yaml
19
+ ignore_rules:
20
+ - missing-vim9script-declaration
21
+ - prefer-def-over-function
22
+ ```
23
+
20
24
  ## Usage
21
25
 
22
26
  ### Command Line
23
27
 
24
- Lint a Vim9 script file:
28
+ Updated vim linter for legacy and vim9script
25
29
 
26
30
  ```bash
27
31
  vinter path/to/your/script.vim
@@ -88,4 +92,4 @@ issues = linter.lint(content)
88
92
  2. Create a feature branch: `git checkout -b my-new-feature`
89
93
  3. Commit your changes: `git commit -am 'Add some feature'`
90
94
  4. Push to the branch: `git push origin my-new-feature`
91
- 5. Submit a pull request
95
+ 5. Submit a pull request
data/bin/vinter CHANGED
File without changes
data/lib/vinter/cli.rb CHANGED
@@ -6,42 +6,70 @@ module Vinter
6
6
 
7
7
  def run(args)
8
8
  if args.empty?
9
- puts "Usage: vim9-lint [file.vim]"
9
+ puts "Usage: vinter [file.vim|directory]"
10
10
  return 1
11
11
  end
12
12
 
13
- file_path = args[0]
13
+ target_path = args[0]
14
14
 
15
- unless File.exist?(file_path)
16
- puts "Error: File not found: #{file_path}"
15
+ unless File.exist?(target_path)
16
+ puts "Error: File or directory not found: #{target_path}"
17
17
  return 1
18
18
  end
19
19
 
20
- content = File.read(file_path)
21
- issues = @linter.lint(content)
20
+ vim_files = if File.directory?(target_path)
21
+ find_vim_files(target_path)
22
+ else
23
+ [target_path]
24
+ end
22
25
 
23
- if issues.empty?
24
- puts "No issues found in #{file_path}"
26
+ if vim_files.empty?
27
+ puts "No .vim files found in #{target_path}"
25
28
  return 0
26
- else
27
- puts "Found #{issues.length} issues in #{file_path}:"
29
+ end
30
+
31
+ total_issues = 0
32
+ error_count = 0
33
+
34
+ vim_files.each do |file_path|
35
+ content = File.read(file_path)
36
+ issues = @linter.lint(content)
37
+ total_issues += issues.length
38
+
39
+ if issues.empty?
40
+ puts "No issues found in #{file_path}" if vim_files.length == 1
41
+ else
42
+ puts "Found #{issues.length} issues in #{file_path}:" if vim_files.length > 1
43
+
44
+ issues.each do |issue|
45
+ type_str = case issue[:type]
46
+ when :error then "ERROR"
47
+ when :warning then "WARNING"
48
+ when :rule then "RULE(#{issue[:rule]})"
49
+ else "UNKNOWN"
50
+ end
28
51
 
29
- issues.each do |issue|
30
- type_str = case issue[:type]
31
- when :error then "ERROR"
32
- when :warning then "WARNING"
33
- when :rule then "RULE(#{issue[:rule]})"
34
- else "UNKNOWN"
35
- end
52
+ line = issue[:line] || 1
53
+ column = issue[:column] || 1
36
54
 
37
- line = issue[:line] || 1
38
- column = issue[:column] || 1
55
+ puts "#{file_path}:#{line}:#{column}: #{type_str}: #{issue[:message]}"
56
+ end
39
57
 
40
- puts "#{file_path}:#{line}:#{column}: #{type_str}: #{issue[:message]}"
58
+ error_count += 1 if issues.any? { |i| i[:type] == :error }
41
59
  end
60
+ end
42
61
 
43
- return issues.any? { |i| i[:type] == :error } ? 1 : 0
62
+ if vim_files.length > 1
63
+ puts "\nProcessed #{vim_files.length} files, found #{total_issues} total issues"
44
64
  end
65
+
66
+ return error_count > 0 ? 1 : 0
67
+ end
68
+
69
+ private
70
+
71
+ def find_vim_files(directory)
72
+ Dir.glob(File.join(directory, "**", "*.vim")).sort
45
73
  end
46
74
  end
47
75
  end
data/lib/vinter/lexer.rb CHANGED
@@ -2,17 +2,18 @@ module Vinter
2
2
  class Lexer
3
3
  TOKEN_TYPES = {
4
4
  # Vim9 specific keywords
5
- keyword: /\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echohl|echomsg|let|execute)\b/,
5
+ keyword: /\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echom|echoerr|echohl|echomsg|let|unlet|execute|exec|continue|break|try|catch|finally|endtry|throw|runtime|silent|delete|command|call|set|setlocal|syntax|highlight|sleep|source|nnoremap|nmap|inoremap|imap|vnoremap|vmap|xnoremap|xmap|cnoremap|cmap|noremap|map)\b/,
6
6
  # Identifiers can include # and special characters
7
7
  identifier: /\b[a-zA-Z_][a-zA-Z0-9_#]*\b/,
8
8
  # Single-character operators
9
- operator: /[\+\-\*\/=<>!&\|\.]/,
9
+ operator: /[\+\-\*\/=%<>!&\|\.]/,
10
10
  # Multi-character operators handled separately
11
- number: /\b\d+(\.\d+)?\b/,
11
+ number: /\b(0[xX][0-9A-Fa-f]+|0[oO][0-7]+|0[bB][01]+|\d+(\.\d+)?([eE][+-]?\d+)?[smh]?)\b/,
12
12
  # Handle both single and double quoted strings
13
- string: /"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/,
13
+ # string: /"(\\"|[^"])*"|'(\\'|[^'])*'/,
14
+ register_access: /@[a-zA-Z0-9":.%#=*+~_\/\-]/,
14
15
  # Vim9 comments use #
15
- comment: /#.*/,
16
+ comment: /(#|").*/,
16
17
  whitespace: /\s+/,
17
18
  brace_open: /\{/,
18
19
  brace_close: /\}/,
@@ -24,9 +25,11 @@ module Vinter
24
25
  semicolon: /;/,
25
26
  comma: /,/,
26
27
  backslash: /\\/,
28
+ question_mark: /\?/,
29
+ command_separator: /\|/,
27
30
  }
28
31
 
29
- CONTINUATION_OPERATORS = %w(. .. + - * / = == != > < >= <= && || ? : -> =>)
32
+ CONTINUATION_OPERATORS = %w(. .. + - * / = == ==# ==? != > < >= <= && || ? : -> =>)
30
33
  def initialize(input)
31
34
  @input = input
32
35
  @tokens = []
@@ -35,9 +38,178 @@ module Vinter
35
38
  @column = 1
36
39
  end
37
40
 
41
+ def should_parse_as_regex
42
+ # Look at recent tokens to determine if we're in a regex context
43
+ recent_tokens = @tokens.last(3)
44
+
45
+ # Check for contexts where regex is expected
46
+ return true if recent_tokens.any? { |t|
47
+ t && t[:type] == :keyword && ['syntax'].include?(t[:value])
48
+ }
49
+
50
+ return true if recent_tokens.any? { |t|
51
+ t && t[:type] == :identifier && ['match', 'region', 'keyword'].include?(t[:value])
52
+ }
53
+
54
+ # Check for comparison operators that often use regex
55
+ return true if recent_tokens.any? { |t|
56
+ t && t[:type] == :operator && ['=~', '!~', '=~#', '!~#', '=~?', '!~?'].include?(t[:value])
57
+ }
58
+
59
+ false
60
+ end
61
+
62
+ def find_unescaped_newline(chunk)
63
+ i = 0
64
+ while i < chunk.length
65
+ if chunk[i] == "\n" && (i == 0 || chunk[i - 1] != '\\')
66
+ return i
67
+ end
68
+ i += 1
69
+ end
70
+ nil # Return nil if no unescaped newline is found
71
+ end
72
+
38
73
  def tokenize
39
74
  until @position >= @input.length
40
75
  chunk = @input[@position..-1]
76
+
77
+ # First check if the line starts with a quote (comment in Vim)
78
+ # Check if we're at the beginning of a line (optionally after whitespace)
79
+ line_start = @position == 0 || @input[@position - 1] == "\n"
80
+ if !line_start
81
+ # Check if we're after whitespace at the start of a line
82
+ temp_pos = @position - 1
83
+ while temp_pos >= 0 && @input[temp_pos] =~ /[ \t]/
84
+ temp_pos -= 1
85
+ end
86
+ line_start = temp_pos < 0 || @input[temp_pos] == "\n"
87
+ end
88
+
89
+ # If we're at the start of a line and it begins with a quote
90
+ if line_start && chunk.start_with?('"')
91
+ # Find the end of the line
92
+ line_end = find_unescaped_newline(chunk) || chunk.length
93
+ comment_text = chunk[0...line_end]
94
+
95
+ @tokens << {
96
+ type: :comment,
97
+ value: comment_text,
98
+ line: @line_num,
99
+ column: @column
100
+ }
101
+
102
+ @position += comment_text.length
103
+ @column += comment_text.length
104
+ next
105
+ end
106
+ # Handle string literals manually
107
+ if chunk.start_with?("'") || chunk.start_with?('"')
108
+ quote = chunk[0]
109
+ i = 1
110
+ escaped = false
111
+ string_value = quote
112
+
113
+ # Keep going until we find an unescaped closing quote
114
+ while i < chunk.length
115
+ char = chunk[i]
116
+ string_value += char
117
+
118
+ if char == '\\' && !escaped
119
+ escaped = true
120
+ elsif (char == "\n" or char == quote) && !escaped
121
+ # Found closing quote
122
+ break
123
+ elsif escaped
124
+ escaped = false
125
+ end
126
+
127
+ i += 1
128
+ end
129
+
130
+ # Add the string token if we found a closing quote
131
+ if i < chunk.length || (i == chunk.length && chunk[-1] == quote)
132
+ @tokens << {
133
+ type: :string,
134
+ value: string_value,
135
+ line: @line_num,
136
+ column: @column
137
+ }
138
+
139
+ @column += string_value.length
140
+ @position += string_value.length
141
+ @line_num += 1 if string_value.include?("\n")
142
+ next
143
+ end
144
+ end
145
+
146
+ # Add special handling for command options in the tokenize method
147
+ if chunk.start_with?('<q-args>', '<f-args>', '<args>')
148
+ arg_token = chunk.match(/\A(<q-args>|<f-args>|<args>)/)[0]
149
+ @tokens << {
150
+ type: :command_arg_placeholder,
151
+ value: arg_token,
152
+ line: @line_num,
153
+ column: @column
154
+ }
155
+ @column += arg_token.length
156
+ @position += arg_token.length
157
+ next
158
+ end
159
+
160
+ # Special handling for a:000 variable arguments array
161
+ if chunk =~ /\Aa:0+/
162
+ varargs_token = chunk.match(/\Aa:0+/)[0]
163
+ @tokens << {
164
+ type: :arg_variable,
165
+ value: varargs_token,
166
+ line: @line_num,
167
+ column: @column
168
+ }
169
+ @column += varargs_token.length
170
+ @position += varargs_token.length
171
+ next
172
+ end
173
+
174
+ # Also add special handling for 'silent!' keyword
175
+ # Add this after the keyword check in tokenize method
176
+ if chunk.start_with?('silent!')
177
+ @tokens << {
178
+ type: :silent_bang,
179
+ value: 'silent!',
180
+ line: @line_num,
181
+ column: @column
182
+ }
183
+ @column += 7
184
+ @position += 7
185
+ next
186
+ end
187
+
188
+ # Check for keywords first, before other token types
189
+ if match = chunk.match(/\A\b(if|else|elseif|endif|while|endwhile|for|endfor|def|enddef|function|endfunction|endfunc|return|const|var|final|import|export|class|extends|static|enum|type|vim9script|abort|autocmd|echoerr|echohl|echomsg|let|unlet|execute|setlocal|syntax|highlight|sleep|source)\b/)
190
+ @tokens << {
191
+ type: :keyword,
192
+ value: match[0],
193
+ line: @line_num,
194
+ column: @column
195
+ }
196
+ @column += match[0].length
197
+ @position += match[0].length
198
+ next
199
+ end
200
+
201
+ # Handle Vim scoped option variables with &l: or &g: prefix
202
+ if match = chunk.match(/\A&[lg]:[a-zA-Z_][a-zA-Z0-9_]*/)
203
+ @tokens << {
204
+ type: :scoped_option_variable,
205
+ value: match[0],
206
+ line: @line_num,
207
+ column: @column
208
+ }
209
+ @column += match[0].length
210
+ @position += match[0].length
211
+ next
212
+ end
41
213
 
42
214
  # Handle Vim option variables with & prefix
43
215
  if match = chunk.match(/\A&[a-zA-Z_][a-zA-Z0-9_]*/)
@@ -51,7 +223,7 @@ module Vinter
51
223
  @position += match[0].length
52
224
  next
53
225
  end
54
-
226
+
55
227
  # Handle Vim special variables with v: prefix
56
228
  if match = chunk.match(/\Av:[a-zA-Z_][a-zA-Z0-9_]*/)
57
229
  @tokens << {
@@ -64,7 +236,7 @@ module Vinter
64
236
  @position += match[0].length
65
237
  next
66
238
  end
67
-
239
+
68
240
  # Handle script-local identifiers with s: prefix
69
241
  if match = chunk.match(/\As:[a-zA-Z_][a-zA-Z0-9_]*/)
70
242
  @tokens << {
@@ -77,7 +249,47 @@ module Vinter
77
249
  @position += match[0].length
78
250
  next
79
251
  end
80
-
252
+
253
+ # Handle buffer-local identifiers with b: prefix
254
+ if match = chunk.match(/\Ab:[a-zA-Z_][a-zA-Z0-9_]*/)
255
+ @tokens << {
256
+ type: :buffer_local,
257
+ value: match[0],
258
+ line: @line_num,
259
+ column: @column
260
+ }
261
+ @column += match[0].length
262
+ @position += match[0].length
263
+ next
264
+ end
265
+
266
+ # Handle window-local identifiers with w: prefix
267
+ if match = chunk.match(/\Aw:[a-zA-Z_][a-zA-Z0-9_]*/)
268
+ @tokens << {
269
+ type: :window_local,
270
+ value: match[0],
271
+ line: @line_num,
272
+ column: @column
273
+ }
274
+ @column += match[0].length
275
+ @position += match[0].length
276
+ next
277
+ end
278
+
279
+ # Handle tab-local identifiers with t: prefix
280
+ if match = chunk.match(/\At:[a-zA-Z_][a-zA-Z0-9_]*/)
281
+ @tokens << {
282
+ type: :tab_local,
283
+ value: match[0],
284
+ line: @line_num,
285
+ column: @column
286
+ }
287
+ @column += match[0].length
288
+ @position += match[0].length
289
+ next
290
+ end
291
+
292
+
81
293
  # Handle global variables with g: prefix
82
294
  if match = chunk.match(/\Ag:[a-zA-Z_][a-zA-Z0-9_]*/)
83
295
  @tokens << {
@@ -90,9 +302,9 @@ module Vinter
90
302
  @position += match[0].length
91
303
  next
92
304
  end
93
-
305
+
94
306
  # Handle argument variables with a: prefix
95
- if match = chunk.match(/\Aa:[a-zA-Z_][a-zA-Z0-9_]*/)
307
+ if match = chunk.match(/\Aa:[a-zA-Z_][a-zA-Z0-9_]*/) || match = chunk.match(/\Aa:[A-Z0-9]/)
96
308
  @tokens << {
97
309
  type: :arg_variable,
98
310
  value: match[0],
@@ -103,11 +315,11 @@ module Vinter
103
315
  @position += match[0].length
104
316
  next
105
317
  end
106
-
107
- # Handle compound assignment operators
108
- if match = chunk.match(/\A(\+=|-=|\*=|\/=|\.\.=)/)
318
+
319
+ # Handle argument variables with a: prefix
320
+ if match = chunk.match(/\Al:[a-zA-Z_][a-zA-Z0-9_]*/)
109
321
  @tokens << {
110
- type: :compound_operator,
322
+ type: :local_variable,
111
323
  value: match[0],
112
324
  line: @line_num,
113
325
  column: @column
@@ -117,10 +329,23 @@ module Vinter
117
329
  next
118
330
  end
119
331
 
120
- # Handle multi-character operators explicitly
121
- if match = chunk.match(/\A(==|!=|=>|->|\.\.|\|\||&&)/)
332
+ # Add support for standalone namespace prefixes (like g:)
333
+ if match = chunk.match(/\A([sgbwtal]):/)
122
334
  @tokens << {
123
- type: :operator,
335
+ type: :namespace_prefix,
336
+ value: match[0],
337
+ line: @line_num,
338
+ column: @column
339
+ }
340
+ @column += match[0].length
341
+ @position += match[0].length
342
+ next
343
+ end
344
+
345
+ # Handle compound assignment operators
346
+ if match = chunk.match(/\A(\+=|-=|\*=|\/=|\.\.=|\.=)/)
347
+ @tokens << {
348
+ type: :compound_operator,
124
349
  value: match[0],
125
350
  line: @line_num,
126
351
  column: @column
@@ -143,6 +368,98 @@ module Vinter
143
368
  next
144
369
  end
145
370
 
371
+ # Handle multi-character operators explicitly
372
+ if match = chunk.match(/\A(=~#|=~\?|=~|!~#|!~\?|!~|==#|==\?|==|!=#|!=\?|!=|=>\?|=>|>=#|>=\?|>=|<=#|<=\?|<=|->#|->\?|->|\.\.|\|\||&&)/)
373
+ @tokens << {
374
+ type: :operator,
375
+ value: match[0],
376
+ line: @line_num,
377
+ column: @column
378
+ }
379
+ @column += match[0].length
380
+ @position += match[0].length
381
+ next
382
+ end
383
+
384
+ # Handle regex patterns /pattern/ - only in specific contexts
385
+ if chunk.start_with?('/') && should_parse_as_regex
386
+ i = 1
387
+ regex_value = '/'
388
+
389
+ # Keep going until we find the closing slash
390
+ while i < chunk.length
391
+ char = chunk[i]
392
+ regex_value += char
393
+
394
+ if char == '/' && (i == 1 || chunk[i-1] != '\\')
395
+ # Found closing slash
396
+ i += 1
397
+ break
398
+ end
399
+
400
+ i += 1
401
+ end
402
+
403
+ # Add the regex token if we found a closing slash
404
+ if regex_value.end_with?('/')
405
+ @tokens << {
406
+ type: :regex,
407
+ value: regex_value,
408
+ line: @line_num,
409
+ column: @column
410
+ }
411
+ @column += regex_value.length
412
+ @position += regex_value.length
413
+ next
414
+ end
415
+ end
416
+
417
+ # Handle hex colors like #33FF33
418
+ if match = chunk.match(/\A#[0-9A-Fa-f]{6}/)
419
+ @tokens << {
420
+ type: :hex_color,
421
+ value: match[0],
422
+ line: @line_num,
423
+ column: @column
424
+ }
425
+ @column += match[0].length
426
+ @position += match[0].length
427
+ next
428
+ end
429
+
430
+ # Handle register access (@a, @", etc.)
431
+ if chunk =~ /\A@[a-zA-Z0-9":.%#=*+~_\/\-]/
432
+ register_token = chunk.match(/\A@[a-zA-Z0-9":.%#=*+~_\/\-]/)[0]
433
+ @tokens << {
434
+ type: :register_access,
435
+ value: register_token,
436
+ line: @line_num,
437
+ column: @column
438
+ }
439
+ @column += register_token.length
440
+ @position += register_token.length
441
+ next
442
+ end
443
+
444
+ # In the tokenize method, add special handling for common mapping components
445
+ if chunk.start_with?('<CR>', '<Esc>', '<Tab>', '<Space>', '<C-') ||
446
+ (chunk =~ /\A<[A-Za-z0-9\-_]+>/)
447
+ # Extract the special key notation
448
+ match = chunk.match(/\A(<[^>]+>)/)
449
+ if match
450
+ special_key = match[1]
451
+ @tokens << {
452
+ type: :special_key,
453
+ value: special_key,
454
+ line: @line_num,
455
+ column: @column
456
+ }
457
+ @position += special_key.length
458
+ @column += special_key.length
459
+ next
460
+ end
461
+ end
462
+
146
463
  # Skip whitespace but track position
147
464
  if match = chunk.match(/\A(\s+)/)
148
465
  whitespace = match[0]
@@ -161,13 +478,35 @@ module Vinter
161
478
  # Handle backslash for line continuation
162
479
  if chunk.start_with?('\\')
163
480
  @tokens << {
164
- type: :backslash,
481
+ type: :line_continuation,
165
482
  value: '\\',
166
483
  line: @line_num,
167
484
  column: @column
168
485
  }
169
486
  @column += 1
170
487
  @position += 1
488
+
489
+ # If followed by a newline, advance to next line
490
+ if @position < @input.length && @input[@position] == "\n"
491
+ @line_num += 1
492
+ @column = 1
493
+ @position += 1
494
+ end
495
+
496
+ next
497
+ end
498
+
499
+ # Check for special case where 'function' is followed by '('
500
+ # which likely means it's used as a built-in function
501
+ if chunk =~ /\Afunction\s*\(/
502
+ @tokens << {
503
+ type: :identifier, # Treat as identifier, not keyword
504
+ value: 'function',
505
+ line: @line_num,
506
+ column: @column
507
+ }
508
+ @column += 'function'.length
509
+ @position += 'function'.length
171
510
  next
172
511
  end
173
512
 
data/lib/vinter/linter.rb CHANGED
@@ -1,7 +1,12 @@
1
+ require "yaml"
2
+
1
3
  module Vinter
2
4
  class Linter
3
- def initialize
5
+ def initialize(config_path: nil)
4
6
  @rules = []
7
+ @ignored_rules = []
8
+ @config_path = config_path || find_config_path
9
+ load_config
5
10
  register_default_rules
6
11
  end
7
12
 
@@ -94,7 +99,7 @@ module Vinter
94
99
  lexer = Lexer.new(content)
95
100
  tokens = lexer.tokenize
96
101
 
97
- parser = Parser.new(tokens)
102
+ parser = Parser.new(tokens, content)
98
103
  result = parser.parse
99
104
 
100
105
  issues = []
@@ -121,8 +126,9 @@ module Vinter
121
126
  }
122
127
  end
123
128
 
124
- # Apply rules
129
+ # Apply rules, ignoring those specified in config
125
130
  @rules.each do |rule|
131
+ next if @ignored_rules.include?(rule.id)
126
132
  rule_issues = rule.apply(result[:ast])
127
133
  issues.concat(rule_issues.map { |i| {
128
134
  type: :rule,
@@ -135,6 +141,25 @@ module Vinter
135
141
 
136
142
  issues
137
143
  end
144
+
145
+ private
146
+
147
+ def find_config_path
148
+ # check for project level config
149
+ project_config = Dir.glob(".vinter{.yaml,.yml,}").first
150
+ project_config if project_config
151
+
152
+ # check for user-level config
153
+ user_config = File.expand_path("~/.vinter")
154
+ user_config if File.exist?(user_config)
155
+ end
156
+
157
+ def load_config
158
+ return unless @config_path && File.exist?(@config_path)
159
+
160
+ config = YAML.load_file(@config_path)
161
+ @ignored_rules = config["ignore_rules"] || []
162
+ end
138
163
  end
139
164
 
140
165
  class Rule