Skip to content

Commit

Permalink
Change treatment of files that fail to load structured data
Browse files Browse the repository at this point in the history
This new approach should allow us to cache the result of the failed
attempt to load structured data on a Lintable. The exception is stored
inside the object, so we can later produce detailed errors.
  • Loading branch information
ssbarnea committed Jan 19, 2023
1 parent ef130ce commit 42f2943
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/ansiblelint/_internal/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
LOAD_FAILURE_MD = """\
# load-failure
Linter failed to process a YAML file, probably because it is either:
"Linter failed to process a file, possible invalid file. Possible reasons:
* contains unsupported encoding (only UTF-8 is supported)
* not an Ansible file
Expand Down Expand Up @@ -175,7 +175,7 @@ class LoadingFailureRule(BaseRule):
"""Failed to load or parse file."""

id = "load-failure"
description = "Linter failed to process a YAML file, possible not an Ansible file."
description = "Linter failed to process a file, possible invalid file."
severity = "VERY_HIGH"
tags = ["core", "unskippable"]
version_added = "v4.3.0"
Expand Down
13 changes: 13 additions & 0 deletions src/ansiblelint/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Constants used by AnsibleLint."""
import os.path
from enum import Enum
from typing import Literal

DEFAULT_RULESDIR = os.path.join(os.path.dirname(__file__), "rules")
Expand Down Expand Up @@ -152,3 +153,15 @@ def main():
#
# https://github.com/ansible/ansible-lint-action/issues/138
GIT_CMD = ["git", "-c", f"safe.directory={os.getcwd()}"]


class States(Enum):
"""States used are used as sentinel values in various places."""

NOT_LOADED = "File not loaded"
LOAD_FAILED = "File failed to load"
UNKNOWN_DATA = "Unknown data"

def __bool__(self) -> bool:
"""Ensure all states evaluate as False as booleans."""
return False
50 changes: 32 additions & 18 deletions src/ansiblelint/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Any, Iterator, cast

# import wcmatch
import wcmatch.pathlib
from wcmatch.wcmatch import RECURSIVE, WcMatch
from yaml.error import YAMLError

from ansiblelint.config import BASE_KINDS, options
from ansiblelint.constants import GIT_CMD, FileType
from ansiblelint.constants import GIT_CMD, FileType, States

if TYPE_CHECKING:
# https://github.com/PyCQA/pylint/issues/3979
Expand Down Expand Up @@ -189,8 +189,9 @@ def __init__(
self.dir: str = ""
self.kind: FileType | None = None
self.stop_processing = False # Set to stop other rules from running
self._data: Any = None
self._data: Any = States.NOT_LOADED
self.line_skips: dict[int, set[str]] = defaultdict(set)
self.exc: Exception | None = None # Stores data loading exceptions

if isinstance(name, str):
name = Path(name)
Expand Down Expand Up @@ -341,21 +342,34 @@ def __repr__(self) -> str:
@property
def data(self) -> Any:
"""Return loaded data representation for current file, if possible."""
if not self._data:
if str(self.base_kind) == "text/yaml":
from ansiblelint.utils import ( # pylint: disable=import-outside-toplevel
parse_yaml_linenumbers,
)

self._data = parse_yaml_linenumbers(self)
# Lazy import to avoid delays and cyclic-imports
if "append_skipped_rules" not in globals():
# pylint: disable=import-outside-toplevel
from ansiblelint.skip_utils import append_skipped_rules

self._data = append_skipped_rules(self._data, self)

# will remain None if we do not know how to load that file-type
if self._data == States.NOT_LOADED:
if self.path.is_dir():
self._data = None
return self._data
try:
if str(self.base_kind) == "text/yaml":
from ansiblelint.utils import ( # pylint: disable=import-outside-toplevel
parse_yaml_linenumbers,
)

self._data = parse_yaml_linenumbers(self)
# Lazy import to avoid delays and cyclic-imports
if "append_skipped_rules" not in globals():
# pylint: disable=import-outside-toplevel
from ansiblelint.skip_utils import append_skipped_rules

self._data = append_skipped_rules(self._data, self)
else:
logging.debug(
"data set to None for %s due to being of %s kind.",
self.path,
self.base_kind,
)
self._data = States.UNKNOWN_DATA

except (RuntimeError, FileNotFoundError, YAMLError) as exc:
self._data = None
self.exc = exc
return self._data


Expand Down
9 changes: 4 additions & 5 deletions src/ansiblelint/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ansiblelint.skip_utils
import ansiblelint.utils
from ansiblelint._internal.rules import LoadingFailureRule
from ansiblelint.constants import States
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable, expand_dirs_in_lintables
from ansiblelint.rules.syntax_check import AnsibleSyntaxCheckRule
Expand Down Expand Up @@ -122,14 +123,12 @@ def run(self) -> list[MatchError]: # noqa: C901
_logger.debug("Excluded %s", lintable)
self.lintables.remove(lintable)
continue
try:
lintable.data
except (RuntimeError, FileNotFoundError) as exc:
if isinstance(lintable.data, States) and lintable.exc:
matches.append(
MatchError(
filename=lintable,
message=str(exc),
details=str(exc.__cause__),
message=str(lintable.exc),
details=str(lintable.exc.__cause__),
rule=LoadingFailureRule(),
)
)
Expand Down

0 comments on commit 42f2943

Please sign in to comment.