Skip to content

Commit e52ead3

Browse files
author
Satoshi MATSUZAKI
committed
temp commit
1 parent 17b9a44 commit e52ead3

22 files changed

+189
-201
lines changed

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ Dockerfile
5353
$ docker build -t sqlint:latest .
5454
...
5555
$ docker run -it sqlint:latset /bin/bash
56-
xxxxx:/work # python3 -m sqlint sqlint/tests/data/query001.sql
57-
sqlint/tests/data/query001.sql:(L2, 6): comma must be head of line
58-
sqlint/tests/data/query001.sql:(L7, 6): comma must be head of line
56+
xxxxx:/work # python3 -m sqlint sqlint/tests/data/query005.sql
57+
sqlint/tests/data/query005.sql:(L2, 6): comma must be head of line
58+
sqlint/tests/data/query005.sql:(L7, 6): comma must be head of line
5959
```
6060

6161
## Checking variations
@@ -93,17 +93,17 @@ Check if sql statement violates following rules.
9393
## Sample
9494

9595
```
96-
$ sqlint sqlint/tests/data/*
97-
sqlint/tests/data/query001.sql:(L2, 6): comma must be head of line
98-
sqlint/tests/data/query001.sql:(L7, 6): comma must be head of line
99-
sqlint/tests/data/query002.sql:(L1, 1): reserved keywords must be lower case: SELECT -> select
100-
sqlint/tests/data/query002.sql:(L3, 7): reserved keywords must be lower case: COUNT -> count
101-
sqlint/tests/data/query003.sql:(L2, 1): indent steps must be 4 multiples (5)
102-
sqlint/tests/data/query004.sql:(L5, 18): too many spaces
103-
sqlint/tests/data/query005.sql:(L2, 7): whitespace must not be after bracket: (
104-
sqlint/tests/data/query005.sql:(L2, 22): whitespace must not be before bracket: )
105-
sqlint/tests/data/query006.sql:(L3, 8): whitespace must be after binary operator: +c
106-
sqlint/tests/data/query006.sql:(L3, 8): whitespace must be after binary operator: b+
96+
$ sqlint sqlint/tests/samples/*
97+
tests/samples/query001.sql (L2, 1): indent steps must be 4 multiples, but 5 spaces
98+
tests/samples/query002.sql (L6, 16): there are multiple whitespaces
99+
tests/samples/query003.sql (L2, 7): whitespace must not be after bracket: (
100+
tests/samples/query003.sql (L2, 22): whitespace must not be before bracket: )
101+
tests/samples/query004.sql (L3, 8): whitespace must be before binary operator: b+
102+
tests/samples/query004.sql (L3, 8): whitespace must be after binary operator: +c
103+
tests/samples/query005.sql (L2, 6): comma must be head of line
104+
tests/samples/query005.sql (L7, 6): comma must be head of line
105+
tests/samples/query006.sql (L1, 1): reserved keywords must be lower case: SELECT -> select
106+
tests/samples/query006.sql (L3, 7): reserved keywords must be lower case: Count -> count
107107
sqlint/tests/data/query007.sql:(L8, 16): table_name must be at the same line as join context
108108
sqlint/tests/data/query008.sql:(L6, 5): join context must be [left outer join], [inner join] or [cross join]: join
109109
sqlint/tests/data/query008.sql:(L10, 10): join context must be [left outer join], [inner join] or [cross join]: left join

src/checker/base.py

Lines changed: 51 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABCMeta, abstractmethod
2-
from typing import Dict, List
2+
from typing import Dict, List, Tuple
33

44
from . import violation
55
from .violation import Violation
@@ -11,7 +11,7 @@
1111
class Checker(metaclass=ABCMeta):
1212
@staticmethod
1313
@abstractmethod
14-
def check(tree: SyntaxTree, config: ConfigLoader) -> List[str]:
14+
def check(tree: SyntaxTree, config: ConfigLoader) -> List[Violation]:
1515
pass
1616

1717

@@ -66,7 +66,7 @@ def _check(node: Node, keyword_style: str) -> List[Violation]:
6666
continue
6767

6868
word: str = token.word
69-
expected: str = KeywordStyleChecker._get_expected(word, keyword_style)
69+
expected: str = KeywordStyleChecker.get_expected(word, keyword_style)
7070
if word != expected:
7171
params = {'index': idx, 'actual': word, 'expected': expected}
7272
v = violation.KeywordStyleViolation(leaf, keyword_style, **params)
@@ -78,7 +78,7 @@ def _check(node: Node, keyword_style: str) -> List[Violation]:
7878
return violation_list
7979

8080
@staticmethod
81-
def _get_expected(keyword: str, keyword_style: str) -> str:
81+
def get_expected(keyword: str, keyword_style: str) -> str:
8282
expected: str = keyword
8383

8484
if keyword_style == 'lower':
@@ -133,7 +133,7 @@ def _check_position(node: Node, comma_position: str) -> List[Violation]:
133133
for idx in comma_indexes:
134134
# If a comma is in brackets, it is appropriate not to break a line at comma.
135135
# So determines that by counting left- and right- brackets at left-right-side.
136-
is_open_bracket = 0 < (tokens[0: idx].count(lb) - tokens[0: idx].count(rb))
136+
is_open_bracket = 0 < (tokens[0:idx].count(lb) - tokens[0:idx].count(rb))
137137
is_close_bracket = 0 < (tokens[idx+1:].count(rb) - tokens[idx+1:].count(lb))
138138

139139
if not is_open_bracket or not is_close_bracket:
@@ -309,7 +309,7 @@ def check(tree: SyntaxTree, config: ConfigLoader) -> List[Violation]:
309309
expected_list = {}
310310
for k, vs in expected_kvs.items():
311311
expected_list[k] = ' '.join([
312-
KeywordStyleChecker._get_expected(v, keyword_style) for v in vs])
312+
KeywordStyleChecker.get_expected(v, keyword_style) for v in vs])
313313

314314
result.extend(JoinChecker._check_context(tree.root, expected_list))
315315

@@ -323,13 +323,15 @@ def _check_table_name(node: Node) -> List[Violation]:
323323
for leaf in node.leaves:
324324
for idx, token in enumerate(leaf.contents):
325325
# ignores except join
326-
if token.kind != Token.KEYWORD or token.word.upper() != 'JOIN':
326+
if token.word.upper() != 'JOIN':
327327
continue
328328

329329
# ignores the token next to 'JOIN' is identifier which may be table.
330-
if idx < len(leaf.contents)-1 and leaf.contents[idx+1].kind == Token.IDENTIFIER:
330+
if idx <= len(leaf.contents)-2 and leaf.contents[idx+2].kind == Token.IDENTIFIER:
331331
continue
332332

333+
# TODO: Checks below
334+
# TODO: SubQueries will become violation in the future.
333335
"""
334336
Ignores the token next to 'Join' is 'Select' (maybe SubQuery)
335337
Examples:
@@ -344,10 +346,6 @@ def _check_table_name(node: Node) -> List[Violation]:
344346
Join (Select id From y)
345347
------
346348
"""
347-
# TODO: SubQueries will be violation in the future.
348-
if idx < len(leaf.contents)-2 and \
349-
('SELECT' in [leaf.contents[idx+1].word.upper(), leaf.contents[idx+2].word.upper()]):
350-
continue
351349

352350
v = violation.JoinTableViolation(leaf, index=idx)
353351
violation_list.append(v)
@@ -395,89 +393,60 @@ def _check_context(node: Node, expected_list: Dict[str, str]) -> List[Violation]
395393
return violation_list
396394

397395

398-
def _check_join_context(line_num, pos, tokens, token_index):
399-
"""
400-
valid contexts
401-
[left outer join], [inner join] or [cross join]
402-
Args:
403-
line_num:
404-
pos:
405-
tokens:
406-
token_index:
407-
408-
Returns:
409-
410-
"""
411-
token = tokens[token_index]
412-
413-
if token.word.upper() != 'JOIN':
414-
return []
415-
416-
# concat join contexts
417-
join_contexts = [token.word]
418-
for tk in reversed(tokens[:token_index]):
419-
if tk.kind == Token.WHITESPACE:
420-
continue
421-
422-
if tk.word.upper() in ['INNER', 'OUTER', 'LEFT', 'RIGHT', 'CROSS']:
423-
join_contexts.insert(0, tk.word)
424-
else:
425-
break
426-
427-
join_context_str = ' '.join(join_contexts)
396+
class LineChecker(Checker):
397+
"""Checks violations about lines management.
428398
429-
if join_context_str.upper() not in ['LEFT OUTER JOIN', 'INNER JOIN', 'CROSS JOIN']:
430-
return ['(L{}, {}): {}: {}'.format(line_num, pos, msg.MESSAGE_JOIN_CONTEXT, join_context_str)]
399+
1. Checks whether two or more blank lines exist.
431400
432-
return []
401+
2. Checks whether breaking line after specified keywords.
402+
Examples:
403+
--- Good ---
404+
Select
405+
x
406+
From
407+
y
408+
------------
433409
434-
435-
def _check_break_line(line_num, pos, tokens, token_index):
410+
---- NG ----
411+
Select x From y
412+
------------
436413
"""
437-
break line at 'and', 'or', 'on' (except between A and B)
438-
Args:
439-
line_num:
440-
pos:
441-
tokens:
442-
token_index:
443-
444-
Returns:
445414

446-
"""
447-
token = tokens[token_index]
415+
@staticmethod
416+
def check(tree: SyntaxTree, config: ConfigLoader) -> List[Violation]:
417+
result: List[Violation] = []
448418

449-
if token.word.upper() not in ['AND', 'OR', 'ON']:
450-
return []
419+
# 1. Checks whether two or more blank lines exist.
420+
blank_count, v_list = LineChecker._check_blank_line(tree.root, blank_count=0)
421+
if blank_count >= 2:
422+
v_list
423+
result.extend()
451424

452-
if _is_first_token(tokens, token_index):
453-
return []
425+
# 2. Checks whether breaking line after specified keywords.
426+
# TODO: Implement
454427

455-
# if AND, search between context
456-
if token.word.upper() == 'AND':
457-
for tk in reversed(tokens[:token_index]):
458-
if tk.word.upper() == 'AND':
459-
break
460-
if tk.word.upper() == 'BETWEEN':
461-
return []
428+
return result
462429

463-
return ['(L{}, {}): {}: {}'.format(line_num, pos, msg.MESSAGE_BREAK_LINE, token.word)]
430+
@staticmethod
431+
def _check_blank_line(node: Node, blank_count: int) -> Tuple[int, List[Violation]]:
432+
violation_list: List[Violation] = []
464433

434+
count = len(node.contents)
465435

466-
def _is_first_token(tokens, token_index):
467-
"""
468-
Args:
469-
tokens:
470-
token_index:
436+
is_blank = (count == 0)
471437

472-
Returns:
438+
if count == 1 and node.contents[0].kind == Token.WHITESPACE:
439+
violation_list.append(violation.OnlyWhitespaceViolation(node, index=0))
440+
is_blank = True
473441

474-
"""
442+
# If this line is not blank and 2 or more previous lines are blank, stack violation.
443+
if is_blank:
444+
blank_count += 1
445+
elif blank_count >= 2:
446+
violation_list.append(violation.MultiBlankLineViolation(node, index=0))
475447

476-
if token_index == 0:
477-
return True
448+
for leaf in node.leaves:
449+
LineChecker._check_blank_line(tree.root, blank_count=0))
478450

479-
for token in tokens[:token_index]:
480-
if token.kind != Token.WHITESPACE and token.kind != Token.COMMENT:
481-
return False
482451

483-
return True
452+
return blank_count, violation_list

src/checker/tree.py

Lines changed: 5 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from src.config.config_loader import ConfigLoader
77

88

9-
def check(tree: SyntaxTree, config: ConfigLoader):
9+
def check(tree: SyntaxTree, config: ConfigLoader) -> List[Violation]:
1010
"""Checks syntax tree and returns error messages
1111
1212
Args:
@@ -30,82 +30,12 @@ def check(tree: SyntaxTree, config: ConfigLoader):
3030
# Check whether comma, which connects some columns or conditions, is head(end) of line.
3131
base.CommaChecker,
3232
# Check about join context
33-
base.JoinChecker
33+
base.JoinChecker,
34+
# Check whether line-breaking before or after specified keywords.
35+
base.LineChecker
3436
]
3537

3638
for checker in checker_list:
3739
violation_list.extend(checker.check(tree, config))
3840

39-
for vi in violation_list:
40-
print(vi.get_message())
41-
42-
# parse sql context
43-
# parsed_tokens = exec_parser(stmt)
44-
"""
45-
blank_line_num = 0
46-
line_num = 1
47-
for tokens in parsed_tokens:
48-
# the position of a head of token in the line.
49-
position = 1
50-
# not suggest to break line as comma in bracket nest
51-
bracket_nest_count = 0
52-
53-
# no tokens or only whitespace
54-
if (len(tokens) == 0 or
55-
(len(tokens) == 1 and tokens[0].kind == Token.WHITESPACE)):
56-
blank_line_num += 1
57-
else:
58-
if blank_line_num >= 2:
59-
result.append('(L{}, {}): {} ({})'.format(line_num, 0, 'too many blank lines', blank_line_num))
60-
blank_line_num = 0
61-
62-
for i, token in enumerate(tokens):
63-
# debug
64-
# logger.debug('L{}:{}: {}'.format(line_num, position, token))
65-
66-
if token.kind == Token.COMMENT:
67-
continue
68-
69-
if token.word == '(':
70-
bracket_nest_count += 1
71-
if token.word == ')':
72-
bracket_nest_count -= 1
73-
74-
# Check whether a whitespace exists not before ) or after (
75-
result.extend(_check_whitespace_brackets(line_num, position, tokens, i))
76-
77-
# Check whether a whitespace exist before and after binary operators
78-
# (e.g.) =, <, >, <=. >=. <>, !=, +, -, *, /, %
79-
result.extend(_check_whitespace_operators(line_num, position, tokens, i))
80-
81-
# Check whether the table name is on the same line as join context
82-
result.extend(_check_join_table(line_num, position, tokens, i))
83-
84-
# Check whether join context is [left outer join], [inner join] or [cross join]
85-
result.extend(_check_join_context(line_num, position, tokens, i))
86-
87-
# Check whether break line at 'and', 'or', 'on' (except between A and B)
88-
if bracket_nest_count == 0:
89-
result.extend(_check_break_line(line_num, position, tokens, i))
90-
91-
# TODO: Not Implemented yet.
92-
# join on での不等号の順番(親テーブル < 日付)
93-
# join on での改行
94-
# 予約語ごとに、適切なインデントがされているか
95-
# サブクエリは外に出す
96-
# エイリアスが予約語と一致していないか
97-
# Suggestion: 定数のハードコーディングをやめる
98-
99-
position += len(token)
100-
101-
line_num += 1
102-
103-
if blank_line_num >= 2:
104-
result.append('(L{}, {}): {} ({})'.format(line_num, 0, 'too many blank lines', blank_line_num))
105-
106-
# Display 'Yey' message if there's no mistake.
107-
if len(result) == 0:
108-
result.append('Yey! You made no mistake🍺')
109-
110-
return result
111-
"""
41+
return violation_list

0 commit comments

Comments
 (0)