Skip to content

Commit 2751f08

Browse files
author
Steve Purcell
committed
Refactored and released as 0.0.4 with tests!
1 parent 1142e05 commit 2751f08

File tree

8 files changed

+131
-14
lines changed

8 files changed

+131
-14
lines changed

Gemfile

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ source "https://rubygems.org"
22

33
gem "pg_query"
44

5+
group :development do
6+
gem "rspec"
7+
end

Gemfile.lock

+15
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4+
diff-lcs (1.2.5)
45
json (1.8.3)
56
pg_query (0.5.0)
67
json (~> 1.8)
8+
rspec (3.2.0)
9+
rspec-core (~> 3.2.0)
10+
rspec-expectations (~> 3.2.0)
11+
rspec-mocks (~> 3.2.0)
12+
rspec-core (3.2.3)
13+
rspec-support (~> 3.2.0)
14+
rspec-expectations (3.2.1)
15+
diff-lcs (>= 1.2.0, < 2.0)
16+
rspec-support (~> 3.2.0)
17+
rspec-mocks (3.2.1)
18+
diff-lcs (>= 1.2.0, < 2.0)
19+
rspec-support (~> 3.2.0)
20+
rspec-support (3.2.2)
721

822
PLATFORMS
923
ruby
1024

1125
DEPENDENCIES
1226
pg_query
27+
rspec
1328

1429
BUNDLED WITH
1530
1.10.3

Rakefile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
begin
2+
require 'rspec/core/rake_task'
3+
RSpec::Core::RakeTask.new(:spec)
4+
rescue LoadError
5+
end
6+
7+
task default: :spec

bin/sqlint

+20-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#!/usr/bin/env ruby
22

3+
# What if 1000+ errors => --limit
4+
# Split on "; -- ..."
5+
6+
7+
38
$LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
49
require 'pg_query'
510
require 'sqlint'
@@ -9,20 +14,22 @@ if ARGV.include?("--version")
914
exit 0
1015
end
1116

17+
ERROR_TYPES = {error: "ERROR", warning: "WARNING"}
18+
1219
ARGV.each do |filename|
13-
contents = File.read(filename)
14-
begin
15-
PgQuery.parse(File.read(filename))
16-
rescue PgQuery::ParseError => e
17-
offset = e.location
18-
before_error = contents[0...(offset-1)]
19-
lines = before_error.split("\n")
20-
line_number = lines.size
21-
column_number = lines.last.size
22-
message_lines = e.message.split("\n")
23-
puts [filename, line_number, column_number, "ERROR " + message_lines.shift].join(":")
24-
message_lines.each do |line|
25-
puts " " + line
20+
File.open(filename, 'r') do |file|
21+
results = SQLint::Linter.new(filename, file).run
22+
results.each do |lint|
23+
message_lines = lint.message.split("\n")
24+
puts [
25+
lint.filename,
26+
lint.line,
27+
lint.column,
28+
ERROR_TYPES[lint.type] + " " + message_lines.shift
29+
].join(":")
30+
message_lines.each do |line|
31+
puts " " + line
32+
end
2633
end
2734
end
2835
end

lib/sqlint.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
require 'sqlint/version'
2+
require 'sqlint/linter'

lib/sqlint/linter.rb

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'pg_query'
2+
3+
module SQLint
4+
class Linter
5+
Lint = Struct.new(:filename, :line, :column, :type, :message)
6+
7+
def initialize(filename, input_stream)
8+
@input = input_stream.read
9+
@filename = filename
10+
end
11+
12+
def run
13+
[].tap do |results|
14+
begin
15+
PgQuery.parse(@input)
16+
rescue PgQuery::ParseError => e
17+
offset = e.location
18+
lines_before_error = @input[0...(offset)].split("\n")
19+
line_number = lines_before_error.size
20+
column_number = lines_before_error.any? ? lines_before_error.last.size : 1
21+
results << Lint.new(@filename, line_number, column_number, :error, e.message)
22+
end
23+
end
24+
end
25+
end
26+
end

lib/sqlint/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SQLint
2-
VERSION = "0.0.3"
2+
VERSION = "0.0.4"
33
end

spec/sqlint_spec.rb

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
require_relative '../lib/sqlint'
2+
3+
RSpec.describe SQLint do
4+
5+
let(:filename) { "some/file/here.sql" }
6+
let(:input) { "SELECT 1" }
7+
let(:input_stream) { StringIO.new(input) }
8+
subject(:linter) { SQLint::Linter.new(filename, input_stream) }
9+
let(:results) { linter.run }
10+
11+
def error(line, col, msg)
12+
SQLint::Linter::Lint.new(filename, line, col, :error, msg)
13+
end
14+
15+
def warning(line, col, msg)
16+
SQLint::Linter::Lint.new(filename, line, col, :warning, msg)
17+
end
18+
19+
context "with empty input" do
20+
let(:input) { "" }
21+
22+
it "reports no errors" do
23+
expect(results).to be_empty
24+
end
25+
end
26+
27+
describe "single errors" do
28+
context "with a single valid statement" do
29+
it "reports no errors" do
30+
expect(results).to be_empty
31+
end
32+
end
33+
34+
context "with a single invalid keyword" do
35+
let(:input) { "WIBBLE" }
36+
it "reports one error" do
37+
expect(results.size).to eq(1)
38+
expect(results.first).to eq(error(1, 1, 'syntax error at or near "WIBBLE"'))
39+
end
40+
end
41+
42+
context "with a single invalid keyword on a later line" do
43+
let(:input) { "SELECT 1;\nWIBBLE" }
44+
it "reports one error" do
45+
expect(results.size).to eq(1)
46+
expect(results.first).to eq(error(2, 1, 'syntax error at or near "WIBBLE"'))
47+
end
48+
end
49+
50+
context "with a single error part-way through a line" do
51+
let(:input) { "SELECT '" }
52+
it "reports one error" do
53+
expect(results.size).to eq(1)
54+
expect(results.first).to eq(error(1, 8, 'unterminated quoted string at or near "\'"'))
55+
end
56+
end
57+
end
58+
end

0 commit comments

Comments
 (0)