Skip to content

Commit

Permalink
Merge branch 'main' into transformer-write-yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
cognifloyd committed Mar 2, 2022
2 parents 8c02e92 + be4704e commit 2a57c35
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 59 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@
("py:class", "ruamel.yaml.comments.CommentedSeq"),
("py:class", "CommentedMap"),
("py:class", "CommentedSeq"),
("py:class", "yamllint.config.YamlLintConfig"),
("py:obj", "Any"),
("py:obj", "ansiblelint.formatters.T"),
]
68 changes: 16 additions & 52 deletions src/ansiblelint/rules/YamllintRule.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import logging
import os
import sys
from typing import TYPE_CHECKING, List

from yamllint.config import YamlLintConfig
from yamllint.linter import run as run_yamllint

from ansiblelint.file_utils import Lintable
from ansiblelint.rules import AnsibleLintRule
from ansiblelint.skip_utils import get_rule_skips_from_line
from ansiblelint.yaml_utils import load_yamllint_config

if TYPE_CHECKING:
from ansiblelint.errors import MatchError

_logger = logging.getLogger(__name__)

YAMLLINT_CONFIG = """
extends: default
rules:
comments:
# https://github.com/prettier/prettier/issues/6780
min-spaces-from-content: 1
# https://github.com/adrienverge/yamllint/issues/384
comments-indentation: false
document-start: disable
# 160 chars was the default used by old E204 rule, but
# you can easily change it or disable in your .yamllint file.
line-length:
max: 160
"""

DESCRIPTION = """\
Rule violations reported by YamlLint when this is installed.
Expand All @@ -48,24 +31,8 @@ class YamllintRule(AnsibleLintRule):
severity = "VERY_LOW"
tags = ["formatting", "yaml"]
version_added = "v5.0.0"
config = None
config = load_yamllint_config()
has_dynamic_tags = True
if "yamllint.config" in sys.modules:
config = YamlLintConfig(content=YAMLLINT_CONFIG)
# if we detect local yamllint config we use it but raise a warning
# as this is likely to get out of sync with our internal config.
for file in [".yamllint", ".yamllint.yaml", ".yamllint.yml"]:
if os.path.isfile(file):
_logger.warning(
"Loading custom %s config file, this extends our "
"internal yamllint config.",
file,
)
config_override = YamlLintConfig(file=file)
config_override.extend(config)
config = config_override
break
_logger.debug("Effective yamllint rules used: %s", config.rules)

def __init__(self) -> None:
"""Construct a rule instance."""
Expand All @@ -79,24 +46,21 @@ def matchyaml(self, file: Lintable) -> List["MatchError"]:
if str(file.base_kind) != "text/yaml":
return matches

if YamllintRule.config:
for p in run_yamllint(
file.content, YamllintRule.config, filepath=file.path
):
self.severity = "VERY_LOW"
if p.level == "error":
self.severity = "MEDIUM"
if p.desc.endswith("(syntax)"):
self.severity = "VERY_HIGH"
matches.append(
self.create_matcherror(
message=p.desc,
linenumber=p.line,
details="",
filename=str(file.path),
tag=p.rule,
)
for p in run_yamllint(file.content, YamllintRule.config, filepath=file.path):
self.severity = "VERY_LOW"
if p.level == "error":
self.severity = "MEDIUM"
if p.desc.endswith("(syntax)"):
self.severity = "VERY_HIGH"
matches.append(
self.create_matcherror(
message=p.desc,
linenumber=p.line,
details="",
filename=str(file.path),
tag=p.rule,
)
)

if matches:
lines = file.content.splitlines()
Expand Down
94 changes: 87 additions & 7 deletions src/ansiblelint/yaml_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Utility helpers to simplify working with yaml-based data."""
import functools
import logging
import os
import re
from io import StringIO
from typing import (
Expand All @@ -26,12 +28,51 @@
from ruamel.yaml.representer import RoundTripRepresenter
from ruamel.yaml.scalarint import ScalarInt
from ruamel.yaml.tokens import CommentToken
from yamllint.config import YamlLintConfig

import ansiblelint.skip_utils
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
from ansiblelint.utils import get_action_tasks, normalize_task, parse_yaml_linenumbers

_logger = logging.getLogger(__name__)

YAMLLINT_CONFIG = """
extends: default
rules:
comments:
# https://github.com/prettier/prettier/issues/6780
min-spaces-from-content: 1
# https://github.com/adrienverge/yamllint/issues/384
comments-indentation: false
document-start: disable
# 160 chars was the default used by old E204 rule, but
# you can easily change it or disable in your .yamllint file.
line-length:
max: 160
"""


@functools.lru_cache(maxsize=1)
def load_yamllint_config() -> YamlLintConfig:
"""Load our default yamllint config and any customized override file."""
config = YamlLintConfig(content=YAMLLINT_CONFIG)
# if we detect local yamllint config we use it but raise a warning
# as this is likely to get out of sync with our internal config.
for file in [".yamllint", ".yamllint.yaml", ".yamllint.yml"]:
if os.path.isfile(file):
_logger.warning(
"Loading custom %s config file, this extends our "
"internal yamllint config.",
file,
)
config_override = YamlLintConfig(file=file)
config_override.extend(config)
config = config_override
break
_logger.debug("Effective yamllint rules used: %s", config.rules)
return config


def iter_tasks_in_file(
lintable: Lintable, rule_id: str
Expand Down Expand Up @@ -460,6 +501,8 @@ def __init__(
):
"""Return a configured ``ruamel.yaml.YAML`` instance.
Some config defaults get extracted from the yamllint config.
``ruamel.yaml.YAML`` uses attributes to configure how it dumps yaml files.
Some of these settings can be confusing, so here are examples of how different
settings will affect the dumped yaml.
Expand Down Expand Up @@ -522,13 +565,14 @@ def __init__(

# NB: We ignore some mypy issues because ruamel.yaml typehints are not great.

# TODO: make some of these dump settings configurable in ansible-lint's options:
# explicit_start, explicit_end, width, indent_sequences, preferred_quote
self.explicit_start: bool = True # type: ignore[assignment]
self.explicit_end: bool = False # type: ignore[assignment]
self.width: int = 120 # type: ignore[assignment]
indent_sequences: bool = True
preferred_quote: str = '"' # either ' or "
config = self._defaults_from_yamllint_config()

# these settings are derived from yamllint config
self.explicit_start: bool = config["explicit_start"] # type: ignore[assignment]
self.explicit_end: bool = config["explicit_end"] # type: ignore[assignment]
self.width: int = config["width"] # type: ignore[assignment]
indent_sequences: bool = cast(bool, config["indent_sequences"])
preferred_quote: str = cast(str, config["preferred_quote"]) # either ' or "

self.default_flow_style = False
self.compact_seq_seq = True # type: ignore[assignment] # dash after dash
Expand Down Expand Up @@ -567,6 +611,42 @@ def __init__(
# lambda self, data: self.represent_scalar("tag:yaml.org,2002:null", "null"),
# )

@staticmethod
def _defaults_from_yamllint_config() -> Dict[str, Union[bool, int, str]]:
"""Extract FormattedYAML-relevant settings from yamllint config if possible."""
config = {
"explicit_start": True,
"explicit_end": False,
"width": 160,
"indent_sequences": True,
"preferred_quote": '"',
}
for rule, rule_config in load_yamllint_config().rules.items():
if not rule_config:
# rule disabled
continue

if rule == "document-start":
config["explicit_start"] = rule_config["present"]
elif rule == "document-end":
config["explicit_end"] = rule_config["present"]
elif rule == "line-length":
config["width"] = rule_config["max"]
elif rule == "indentation":
indent_sequences = rule_config["indent-sequences"]
# one of: bool, "whatever", "consistent"
# so, we use True for "whatever" and "consistent"
config["indent_sequences"] = bool(indent_sequences)
elif rule == "quoted-strings":
quote_type = rule_config["quote-type"]
# one of: single, double, any
if quote_type == "single":
config["preferred_quote"] = "'"
elif quote_type == "double":
config["preferred_quote"] = '"'

return cast(Dict[str, Union[bool, int, str]], config)

@property # type: ignore[override]
def version(self) -> Union[str, Tuple[int, int]]: # type: ignore[override]
"""Return the YAML version used to parse or dump.
Expand Down

0 comments on commit 2a57c35

Please sign in to comment.