Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .relint.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
- name: No ToDo
pattern: "[tT][oO][dD][oO]"
pattern: '[tT][oO][dD][oO]'
hint: Get it done right away!
filename:
- "*.py"
- "*.js"
filePattern: ^(?!.*test_).*\.(py|js)$
error: false
36 changes: 14 additions & 22 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,17 @@ You can write your own regular rules in a YAML file, like so:
.. code-block:: YAML

- name: No ToDo
pattern: "[tT][oO][dD][oO]"
pattern: '[tT][oO][dD][oO]'
hint: Get it done right away!
filename:
- "*.py"
- "*.js"
filePattern: .*\.(py|js)
error: false

The ``name`` attribute is the name of your linter, the ``pattern`` can be
any regular expression. The linter does lint entire files, therefore your
expressions can match multiple lines and include newlines.

You can narrow down the file types your linter should be working with, by
providing the optional ``filename`` attribute. The default is ``*``.
providing the optional ``filePattern`` attribute. The default is ``.*``.

The optional `error` attribute allows you to only show a warning but not exit
with a bad (non-zero) exit code. The default is `true`.
Expand All @@ -56,10 +54,10 @@ If you prefer linting changed files (cached on git) you can use the option
This option is useful for pre-commit purposes. Here an example of how to use it
with `pre-commit`_ framework:

.. code-block:: YAML
.. code-block:: yaml

- repo: https://github.com/codingjoe/relint
rev: 0.5.0
rev: 1.2.0
hooks:
- id: relint

Expand All @@ -71,30 +69,24 @@ Samples
.. code-block:: yaml

- name: db fixtures
pattern: "def test_[^(]+\\([^)]*(customer|product)(, |\\))"
pattern: 'def test_[^(]+\([^)]*(customer|product)(, |\))'
hint: Use model_mommy recipies instead of db fixtures.
filename:
- "**/test_*.py"
filePattern: test_.*\.py

- name: model_mommy recipies
pattern: "mommy\\.make\\("
pattern: mommy\.make\(
hint: Please use mommy.make_recipe instead of mommy.make.
filename:
- "**/test_*.py"
- "conftest.py"
- "**/conftest.py"
filePattern: (test_.*|conftest)\.py

- name: the database is lava
pattern: "@pytest.fixture.*\\n[ ]*def [^(]+\\([^)]*(db|transactional_db)(, |\\))"
pattern: '@pytest.fixture.*\n[ ]*def [^(]+\([^)]*(db|transactional_db)(, |\))'
hint: Please do not create db fixtures but model_mommy recipies instead.
filename:
- "*.py"
filePattern: .*\.py

- name: No logger in management commands
pattern: "(logger|import logging)"
hint: "Please write to self.stdout or self.stderr in favor of using a logger."
filename:
- "*/management/commands/*.py"
pattern: (logger|import logging)
hint: Please write to self.stdout or self.stderr in favor of using a logger.
filePattern: \/management\/commands\/.*\.py

.. _`pre-commit`: https://pre-commit.com/
.. _`relint-pre-commit.sh`: relint-pre-commit.sh
54 changes: 42 additions & 12 deletions relint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import glob
import re
import sys
import warnings
from collections import namedtuple
from itertools import chain

Expand All @@ -16,10 +17,19 @@
r"(?:\n|^)diff --git a\/.* b\/.*(?:\n|$)")


Test = namedtuple('Test', ('name', 'pattern', 'hint', 'filename', 'error'))
Test = namedtuple(
'Test', (
'name',
'pattern',
'hint',
'file_pattern',
'filename',
'error',
)
)


def parse_args():
def parse_args(args):
parser = argparse.ArgumentParser()
parser.add_argument(
'files',
Expand All @@ -42,19 +52,29 @@ def parse_args():
action='store_true',
help='Analyze content from git diff.'
)
return parser.parse_args()
return parser.parse_args(args=args)


def load_config(path):
with open(path) as fs:
for test in yaml.safe_load(fs):
filename = test.get('filename', ['*'])
if not isinstance(filename, list):
filename = list(filename)
filename = test.get('filename')
if filename:
warnings.warn(
"The glob style 'filename' configuration attribute has been"
" deprecated in favor of a new RegEx based 'filePattern' attribute."
" 'filename' support will be removed in relint version 2.0.",
DeprecationWarning
)
if not isinstance(filename, list):
filename = list(filename)
file_pattern = test.get('filePattern', '.*')
file_pattern = re.compile(file_pattern)
yield Test(
name=test['name'],
pattern=re.compile(test['pattern'], re.MULTILINE),
hint=test.get('hint'),
file_pattern=file_pattern,
filename=filename,
error=test.get('error', True)
)
Expand All @@ -68,10 +88,16 @@ def lint_file(filename, tests):
pass
else:
for test in tests:
if any(fnmatch.fnmatch(filename, fp) for fp in test.filename):
for match in test.pattern.finditer(content):
line_number = match.string[:match.start()].count('\n') + 1
yield filename, test, match, line_number
if test.filename:
if any(fnmatch.fnmatch(filename, fp) for fp in test.filename):
for match in test.pattern.finditer(content):
line_number = match.string[:match.start()].count('\n') + 1
yield filename, test, match, line_number
else:
if test.file_pattern.match(filename):
for match in test.pattern.finditer(content):
line_number = match.string[:match.start()].count('\n') + 1
yield filename, test, match, line_number


def parse_line_numbers(output):
Expand Down Expand Up @@ -183,8 +209,8 @@ def parse_diff(output):
return changed_content


def main():
args = parse_args()
def main(args=sys.argv):
args = parse_args(args)
paths = {
path
for file in args.files
Expand All @@ -207,5 +233,9 @@ def main():
exit(exit_code)


if not sys.warnoptions:
warnings.simplefilter('default')


if __name__ == '__main__':
main()
24 changes: 19 additions & 5 deletions test_relint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import io
import sys
import warnings

import pytest

Expand All @@ -10,10 +11,8 @@
class TestMain:
@pytest.mark.parametrize('filename', ['test_relint.py', '[a-b].py', '[b-a].py'])
def test_main_execution(self, mocker, filename):
mocker.patch.object(sys, 'argv', ['relint.py', filename])

with pytest.raises(SystemExit) as exc_info:
main()
main(['relint.py', filename])

assert exc_info.value.code == 0

Expand All @@ -26,12 +25,11 @@ def test_main_execution_with_diff(self, capsys, mocker, tmpdir):
"+# TODO do something"
)

mocker.patch.object(sys, 'argv', ['relint.py', 'dummy.py', '--diff'])
mocker.patch.object(sys, 'stdin', diff)

with tmpdir.as_cwd():
with pytest.raises(SystemExit) as exc_info:
main()
main(['relint.py', 'dummy.py', '--diff'])

expected_message = 'Hint: Get it done right away!'

Expand Down Expand Up @@ -144,3 +142,19 @@ def test_parse_complete_diff(self):
expected = {'test_relint.py': [2]}

assert parsed_content == expected

def test_filename_warning(self, tmpdir):
tmpdir.join('.relint.yml').write(
'- name: old\n'
' pattern: ".*"\n'
' filename: "*.py"\n'
)

with tmpdir.as_cwd():
with warnings.catch_warnings(record=True) as w:
with pytest.raises(SystemExit) as exc_info:
main(['**'])

assert exc_info.value.code == 0
assert issubclass(w[-1].category, DeprecationWarning)
assert "'filename'" in str(w[-1].message)