Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#177: Forbid to use CRLF end-of-line #708

Merged
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
We follow Semantic Versions.


## Version 0.6.0

### Features

- Forbid to use `\r\n` (CRLF) end-of-line


## Version 0.5.0

### Features
Expand Down
5 changes: 4 additions & 1 deletion dotenv_linter/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ def run(self) -> None:
def _prepare_file_contents(self) -> Iterator[Tuple[str, str]]:
"""Returns iterator with each file contents."""
for filename in self._filenames: # TODO: move this logic from here
with open(filename, encoding='utf8') as file_object:
# From `open` docs on `newline` - If it is '', universal
# newline mode is enabled but line endings are returned
# to the caller untranslated
with open(filename, encoding='utf8', newline='') as file_object:
yield filename, file_object.read()

def _prepare_fst(
Expand Down
22 changes: 22 additions & 0 deletions dotenv_linter/violations/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

.. autoclass:: SpacedValueViolation
.. autoclass:: QuotedValueViolation
.. autoclass:: InvalidEOLViolation

"""

Expand Down Expand Up @@ -62,3 +63,24 @@ class QuotedValueViolation(BaseFSTViolation):

code = 301
error_template = 'Found quoted value'


@final
class InvalidEOLViolation(BaseFSTViolation):
r"""
Restricts to use `\r\n` (CRLF) end-of-line.

Reasoning:
Mixing different end-of-line chars can lead to different
hard-to-debug problems.

Solution:
Use `\n` (LF) end-of-line.
Another option is to add line `text eol=lf` to `.gitattributes`.

.. versionadded:: 0.6.0

"""

code = 302
error_template = 'Found CRLF end-of-line'
12 changes: 11 additions & 1 deletion dotenv_linter/visitors/fst/values.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from typing import final
from typing import Final, final

from dotenv_linter.grammar.fst import Value
from dotenv_linter.violations.values import (
InvalidEOLViolation,
QuotedValueViolation,
SpacedValueViolation,
)
from dotenv_linter.visitors.base import BaseFSTVisitor

CRLF_EOL: Final = '\r'


@final
class ValueVisitor(BaseFSTVisitor):
Expand All @@ -19,10 +22,13 @@ def visit_value(self, node: Value) -> None:
Raises:
QuotedValueViolation
SpacedValueViolation
InvalidEOLViolation

"""
self._check_value_quotes(node)
self._check_value_spaces(node)
self._is_crlf_eol_used(node)

self.generic_visit(node)

def _check_value_spaces(self, node: Value) -> None:
Expand All @@ -35,3 +41,7 @@ def _check_value_quotes(self, node: Value) -> None:
self._add_violation(QuotedValueViolation(node, text=node.raw_text))
elif text.startswith("'") and text.endswith("'"):
self._add_violation(QuotedValueViolation(node, text=node.raw_text))

def _is_crlf_eol_used(self, node: Value) -> None:
if node.raw_text.endswith(CRLF_EOL):
self._add_violation(InvalidEOLViolation(node, text=node.text))
1 change: 1 addition & 0 deletions tests/fixtures/.env.correct
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
# See: https://github.com/wemake-services/dotenv-linter/issues/20
TEST=1
EMPTY=
VARIABLE_WITH_TRAILING_SLASH_R=value\r
26 changes: 25 additions & 1 deletion tests/test_cli/test_lint_command.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import subprocess
from pathlib import Path

from dotenv_linter.violations.values import InvalidEOLViolation


def test_lint_correct_fixture(fixture_path):
Expand Down Expand Up @@ -74,5 +77,26 @@ def test_lint_wrong_fixture(fixture_path, all_violations):

assert process.returncode == 1

for violation_class in all_violations:
violations_without_eol = set(all_violations) - {InvalidEOLViolation}
for violation_class in violations_without_eol:
assert str(violation_class.code) in stderr


def test_lint_wrong_eol(tmp_path: Path) -> None:
"""Checks that `lint` command works for with with CRLF end-of-line."""
temp_file = tmp_path / '.env.temp'
temp_file.write_text('VARIABLE_WITH_CRLF_EOL=123\r\n')

process = subprocess.Popen(
['dotenv-linter', temp_file.absolute()],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
# It is important to set this to `False`, otherwise eol are normalized
universal_newlines=False,
encoding='utf8',
)
_, stderr = process.communicate()

assert process.returncode == 1

assert str(InvalidEOLViolation.code) in stderr