diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..b9e41de --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = src/djlint +branch = True + + +[report] +show_missing = True +skip_covered = True +omit = + */test* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..54f9758 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: test +on: [push] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9] + runs-on: ${{ matrix.os }} + steps: + - name: checkout + uses: actions/checkout@v2 + - name: setup python ${{ matrix.python-version }} on ${{ matrix.os }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: install deps + run: python -m pip install tox + - name: test + run: tox -p + - name: upload cov + uses: codecov/codecov-action@v1 + with: + files: ./coverage.xml + fail_ci_if_error: true + verbose: true diff --git a/.gitignore b/.gitignore index ba2a201..adca2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +report.xml # Created by https://www.toptal.com/developers/gitignore/api/macos,python # Edit at https://www.toptal.com/developers/gitignore?templates=macos,python diff --git a/setup.py b/setup.py index 58cfc0b..09f13d2 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,12 @@ def long_description(): ) +test_deps = ["coverage", "pytest", "pytest-xdist", "pytest-cov"] + +extras = { + "test": test_deps, +} + setup( name="djlint", version="0.0.8", @@ -35,14 +41,13 @@ setup( package_dir={"": "src"}, packages=find_packages(where="src"), python_requires=">=3.6", - install_requires=["click>=7.1.2", "pyyaml>=5.4.1"], + install_requires=["click>=7.1.2", "pyyaml>=5.4.1", "colorama>=0.4.3"], test_suite="tests.test_djlint", - extras_require={ - "colorama": ["colorama>=0.4.3"], - }, entry_points={ "console_scripts": [ "djlint=djlint:main", ] }, + tests_require=test_deps, + extras_require=extras, ) diff --git a/src/djlint/__init__.py b/src/djlint/__init__.py index 14d68bd..1076e55 100644 --- a/src/djlint/__init__.py +++ b/src/djlint/__init__.py @@ -30,12 +30,12 @@ def lint_file(this_file: Path): html = this_file.read_text(encoding="utf8") # build list of line ends for file - line_ends = [m.end() for m in re.finditer(r".*\n", html)] + line_ends = [m.end() for m in re.finditer(r"(?:.*\n)|(?:[^\n]+$)", html)] def get_line(start): """Get the line number and index of match.""" for index, value in enumerate(line_ends): - if value >= start: + if value > start: line_start_index = (line_ends[index - 1] if index > 0 else 0) - 1 return "%d:%d" % (index + 1, start - line_start_index) @@ -69,7 +69,7 @@ def get_src(src: Path, extension=None): paths = list(src.glob(r"**/*.%s" % extension)) if len(paths) == 0: - echo("No files to lint! 😢") + echo(Fore.BLUE + "No files to lint! 😢") return [] return paths @@ -124,7 +124,9 @@ def main(src: str, extension: str): + Style.RESET_ALL ) error_count += len(errors) - for message in sorted(errors, key=lambda x: x["line"]): + for message in sorted( + errors, key=lambda x: int(x["line"].split(":")[0]) + ): error = bool(message["code"][:1] == "E") echo( "{} {} {} {} {}".format( diff --git a/src/djlint/rules.yaml b/src/djlint/rules.yaml index 7b6932e..5ae4f16 100644 --- a/src/djlint/rules.yaml +++ b/src/djlint/rules.yaml @@ -26,7 +26,7 @@ name: W005 message: Html tag should have lang attribute. patterns: - - + - - rule: name: W006 message: Img tag should have alt, height and width attributes. diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bad.html.dj2 b/tests/bad.html.dj2 new file mode 100644 index 0000000..1a18979 --- /dev/null +++ b/tests/bad.html.dj2 @@ -0,0 +1 @@ + diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6e18a59 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,15 @@ +import tempfile + +import pytest +from click.testing import CliRunner + + +@pytest.fixture +def runner(): + yield CliRunner() + + +@pytest.fixture +def tmp_file(): + with tempfile.NamedTemporaryFile() as tmp: + yield tmp diff --git a/tests/test_djlint.py b/tests/test_djlint.py index e69de29..56c2e3f 100644 --- a/tests/test_djlint.py +++ b/tests/test_djlint.py @@ -0,0 +1,202 @@ +"""Djlint Tests. + +run:: + + coverage erase; coverage run -m pytest; coverage report -m + +or:: + + tox +""" + + +from src.djlint import main as djlint + + +def test_help(runner): + result = runner.invoke(djlint, ["-h"]) + assert result.exit_code == 0 + assert "Djlint django template files." in result.output + + +def test_bad_args(runner): + result = runner.invoke(djlint, ["-a"]) + assert result.exit_code == 2 + assert "Error: No such option: -a" in result.output + + result = runner.invoke(djlint, ["--aasdf"]) + assert result.exit_code == 2 + assert "Error: No such option: --aasdf" in result.output + + +def test_bad_file(runner): + result = runner.invoke(djlint, ["not_a_file.html"]) + assert result.exit_code == 2 + assert "Path 'not_a_file.html' does not exist." in result.output + + +def test_good_file(runner): + result = runner.invoke(djlint, ["tests/bad.html"]) + assert result.exit_code == 0 + assert "tests/bad.html" in result.output + + +def test_bad_path(runner): + result = runner.invoke(djlint, ["tests/nowhere"]) + assert result.exit_code == 2 + assert "Path 'tests/nowhere' does not exist." in result.output + + +def test_good_path_with_ext(runner): + result = runner.invoke(djlint, ["tests/", "-e", "html"]) + assert result.exit_code == 0 + assert "tests/bad.html" in result.output + + result = runner.invoke(djlint, ["tests/", "--extension", "html*"]) + assert result.exit_code == 0 + assert "tests/bad.html" in result.output + assert "tests/bad.html.dj" in result.output + + +def test_good_path_with_bad_ext(runner): + result = runner.invoke(djlint, ["tests/", "-e", "html.alphabet"]) + assert result.exit_code == 0 + assert "No files to lint!" in result.output + + +def test_empty_file(runner, tmp_file): + tmp_file.write(b"") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + + +def test_E001(runner, tmp_file): + tmp_file.write(b"{{test }}\n{% test%}") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "E001 1:" in result.output + assert "E001 2:4" in result.output + + +def test_E002(runner, tmp_file): + tmp_file.write(b"{% extends 'this' %}") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "E002 1:" in result.output + + +def test_W003(runner, tmp_file): + tmp_file.write(b"{% endblock %}") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W003 1:" in result.output + + +def test_W004(runner, tmp_file): + tmp_file.write(b'') + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W004 1:" in result.output + + +def test_W005(runner, tmp_file): + tmp_file.write(b"\n") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W005 2:" in result.output + + +def test_W006(runner, tmp_file): + tmp_file.write(b"") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W006 1:" in result.output + + +def test_W007(runner, tmp_file): + tmp_file.write(b'') + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W007 1:" in result.output + + +def test_W008(runner, tmp_file): + tmp_file.write(b"
") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W008 1:" in result.output + + +def test_W009(runner, tmp_file): + tmp_file.write(b"

") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W009 1:" in result.output + + +def test_W010(runner, tmp_file): + tmp_file.write(b'') + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W010 1:" in result.output + + +def test_W011(runner, tmp_file): + tmp_file.write(b"
") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W011 1:" in result.output + + +def test_W012(runner, tmp_file): + tmp_file.write(b'
') + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W012 1:" in result.output + + +def test_W013(runner, tmp_file): + tmp_file.write( + b"this is a very long line of random long text that is very long and should not be so long, hopefully it thows an error somwhere" + ) + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W013 1:" in result.output + + +def test_W014(runner, tmp_file): + tmp_file.write(b"
\n\n\n

") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W014 1:" in result.output + + +def test_W015(runner, tmp_file): + tmp_file.write(b"

") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W015 1:" in result.output + + +def test_W016(runner, tmp_file): + tmp_file.write(b"\nstuff\n") + tmp_file.seek(0) + result = runner.invoke(djlint, [tmp_file.name]) + assert result.exit_code == 0 + assert "W016 1:" in result.output diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..3da4106 --- /dev/null +++ b/tox.ini @@ -0,0 +1,28 @@ +[tox] +envlist = + clean, + py{36,37,38,39} + cov +skip_missing_interpreters = True +isolated_build = True + +[testenv:clean] +skip_install: true +deps = coverage +commands = coverage erase + +[testenv] +deps = .[test] +commands = + pytest -n 0 --cov --cov-append --cov-report=term-missing --disable-warnings --junitxml=report.xml +depends = + py{36,37,38,39}: clean + cov: py{36,37,38,39} + + +[testenv:cov] +skip_install: true +deps = coverage +commands = + coverage report -m + coverage xml