Compare commits

..

65 Commits

Author SHA1 Message Date
Adrien Vergé
a7d39b5492 yamllint version 0.6.0 2016-01-25 11:03:00 +01:00
Adrien Vergé
4410bc3e23 Rules: indentation: Fix check-multi-line-strings
For strings that continue on next line at a lower indentation level:

    Blaise Pascal: Je vous écris une longue lettre parce que
      je n'ai pas le temps d'en écrire une courte.
2016-01-25 11:01:42 +01:00
Adrien Vergé
97c446907c Rules: line-length: Add option allow-non-breakable-words 2016-01-24 22:46:10 +01:00
Adrien Vergé
376a6ed484 Doc: Enhance short description 2016-01-24 18:40:48 +01:00
Adrien Vergé
a1eb9d7d2f yamllint version 0.5.2 2016-01-24 18:07:36 +01:00
Adrien Vergé
45538fb08a Doc: Explicit installation by adding sudo in README 2016-01-24 18:05:27 +01:00
Adrien Vergé
be998593dd Distribution: Create script with setup.py 2016-01-24 18:02:42 +01:00
Adrien Vergé
5ed496f471 Distribution: Remove unneeded setup_requires
With the new project layout, `pyyaml` is not needed anymore for parsing
setup.py.
2016-01-24 17:57:11 +01:00
Adrien Vergé
dbbecb5875 Refactor project layout to import yamllint alone
Currently importing yamllint recursively imports its submodules, which
finally requires having pyyaml installed. This is a problem when you
just want to import APP_VERSION from yamllint. For instance, setup.py
imports yamllint to know the version, but doesn't know yet that pyyaml
is to be installed, because it is stated in setup.py itself.

To solve this, yamllint/__init__.py will only contain constants. The
linting functions will be in yamllint/linter.py.
2016-01-24 17:48:20 +01:00
Adrien Vergé
7b147cb411 Tests: Remove Python 2.6 from CI tests
Because:

1. It is old. VERY old.

2. Some useful methods (`assertRaisesRegexp`, `assertIsInstance`) are
   only available from Python 2.7.
2016-01-24 17:39:36 +01:00
Adrien Vergé
fc108e7cee Config: Refactor to use YamlLintConfig objects 2016-01-24 17:39:27 +01:00
Adrien Vergé
792bdf99b4 yamllint version 0.5.1 2016-01-24 15:03:38 +01:00
Adrien Vergé
92798dbda9 Distribution: Add new keywords 2016-01-24 15:03:38 +01:00
Adrien Vergé
e3ebea6033 Distribution: Fix broken setup_requires
The `pyyaml` dependency is needed in `install_requires` but also in
`setup_requires`, because running `setup.py` requires importing
`yamllint`, which itself imports `yaml`.
2016-01-24 14:59:32 +01:00
Adrien Vergé
7983c66093 Doc: Clarify Python compatibility in README 2016-01-23 14:32:02 +01:00
Adrien Vergé
fee72d484e Doc: Add a screenshot 2016-01-23 14:30:24 +01:00
Adrien Vergé
387d14f816 yamllint version 0.5.0 2016-01-22 19:10:05 +01:00
Adrien Vergé
ba8a9d0ba1 Doc: Give an explicit link from configuration to rules 2016-01-22 18:42:03 +01:00
Adrien Vergé
26b5364be4 Doc: Add Read The Docs status badge in README 2016-01-22 18:20:31 +01:00
Adrien Vergé
47d6534e75 Doc: Write the configuration page 2016-01-22 18:20:31 +01:00
Adrien Vergé
237db5aeef Doc: Document how to use the yamllint Python module 2016-01-22 18:20:31 +01:00
Adrien Vergé
6e9de02eac Doc: Update index
Add a brief description and remove unused links.
2016-01-22 18:20:31 +01:00
Adrien Vergé
044c049462 Doc: Document rules 2016-01-22 18:20:31 +01:00
Adrien Vergé
48589176c7 Doc: Convert README.md to README.rst 2016-01-22 18:20:31 +01:00
Adrien Vergé
38234a1d3c Doc: Generate documentation with Sphinx
HTML documentation should be built with sphinx. This enables easy
integration with Read The Docs [1]. It can also be generated manually by
running:

    make -C docs html

A man page can be generated by running:

    make -C docs man

[1]: http://yamllint.readthedocs.org/
2016-01-22 18:20:28 +01:00
Adrien Vergé
1bfd18097a Rules: indentation: Add 'check-multi-line-strings' option
This options allows the user to control whether to lint indentation
inside multi-line scalars or not.

When enabled, such YAML source will be detected as a problem:

    - C code: void main() {
                  printf("foo");
              }

whereas this one would not:

    - C code: void main() {
              printf("foo");
              }
2016-01-22 14:23:37 +01:00
Adrien Vergé
08f99ccc19 Rules: new-lines: Force type to be in ('unix', 'dos') 2016-01-21 21:59:53 +01:00
Adrien Vergé
7b6f024448 yamllint version 0.4.0 2016-01-20 18:18:35 +01:00
Adrien Vergé
75b4758c95 cli: 'standard' format: Print filename only when error 2016-01-20 17:55:54 +01:00
Adrien Vergé
0e98df2643 cli: Allow passing directories as arguments
For instance:

    yamllint .
    yamllint file.yml ../my-other-dir
2016-01-20 17:55:54 +01:00
Adrien Vergé
d4189083d0 Introduce the 'cli' module and call it from the script 2016-01-20 17:39:26 +01:00
Adrien Vergé
67d13d60ae Rules: indentation: Check multi-line scalars 2016-01-20 17:39:11 +01:00
Adrien Vergé
96465008ab Rules: Fix spaces_before when prev is multi-line scalar
YAML content like the following one produced an error, because the
multi-line ScalarToken ends at the beginning of the 4th line (the one
with the value):

    ? >
        multi-line
        key
    : value
2016-01-20 17:38:48 +01:00
Adrien Vergé
847f7e3fff Rules: comments: Fix bug when multi-line scalar
YAML content like the following one produced an error, because the
ScalarToken associated whose value is "this is plain text" ends at the
beginning of the 5th line (the one with the comment):

    ---
    string: >
      this is plain text

    # comment
2016-01-20 10:45:59 +01:00
Adrien Vergé
6a24781f96 Tests: indentation: Add explicit keys test cases 2016-01-20 10:45:52 +01:00
Adrien Vergé
33224a04e4 yamllint version 0.3.0 2016-01-19 22:58:59 +01:00
Adrien Vergé
fd9d2a00ff Doc: Update README with examples 2016-01-19 22:57:12 +01:00
Adrien Vergé
0b0251bacc Rules: indentation: Add the 'indent-sequences' option
Using either 'yes', 'no' or 'whatever', the user will be able to choose
whether to force block sequence items to be indented, to force them not
to be indented, or don't care, respectively.
2016-01-19 22:37:58 +01:00
Adrien Vergé
ad5cec9c6c Config: Allow overriding only one option when extending 2016-01-19 21:49:58 +01:00
Adrien Vergé
fb14cbdbd9 Config: Allow options to be in a pre-defined list 2016-01-19 21:12:11 +01:00
Adrien Vergé
8288a6f331 Rules: colons: Apply to '?' also 2016-01-19 19:45:13 +01:00
Adrien Vergé
9d8b0d4d2c Rules: commas: Don't allow a comma on a new line
Forbid such constructions:

    [ a, b, c
      , d, e ]
2016-01-19 19:42:56 +01:00
Adrien Vergé
39c878c819 Rules: indentation: Rewrite the algorithm (again)
Use a new, better thought algorithm that keeps an history stack with all
the parents indentations.
2016-01-19 19:42:56 +01:00
Adrien Vergé
222f7a27c1 Make syntax errors prevail over all yamllint problems 2016-01-19 17:18:57 +01:00
Adrien Vergé
effb4db3b4 Tests: Rules: Remove unused line and column args
Now that every test case use the `problem=(x, y)` syntax.
2016-01-19 17:18:57 +01:00
Adrien Vergé
d617eb70ae Rules: Keep a persistent context for token rules
This will be needed to build a clean indentation checking algorithm.
2016-01-19 17:18:57 +01:00
Adrien Vergé
f09aef4f89 Rules: comments-indentation: Allow two levels
Previously only comments that were indented like the following content
line were allowed, e.g.:

    prev: line:
      # commented line
      current: line

With this change, such new cases are also allowed:

      prev: line
      # commented line 1
    # commented line 2
    current: line
2016-01-19 17:18:57 +01:00
Adrien Vergé
01c12f2462 Syntax errors: Use the BaseLoader for safety 2016-01-19 16:35:57 +01:00
Adrien Vergé
918f15b68d Make syntax errors prevail over yamllint 'warnings'
When both a syntax error (unability to parse a document) and a cosmetic
yamllint problem are found at the same place, the yamllint problem had
the priority -- and the syntax error was not displayed.

This had the following problem: if a rule is at the 'warning' level, its
problems will not make the `yamllint` script return a failure return
code (`!= 0`), even when it should (because there was a syntax error,
precisely).

This commit changes this behavior by preferring yamllint problems only
when they have the 'error' level.
2016-01-15 18:46:49 +01:00
Adrien Vergé
97e2210ec9 Don't treat non-importable YAML as syntax error
`yaml.load()` exceptions are not necessarily syntax errors. For
instance, the following YAML source cannot be `load()`ed into a Python
object, but is valid nonetheless:

    ? - Detroit Tigers
      - Chicago cubs
    :
      - 2001-07-23

    ? [ New York Yankees,
        Atlanta Braves ]
    : [ 2001-07-02, 2001-08-12,
        2001-08-14 ]

This commit detects syntax errors from `yaml.parse()` exceptions rather
than `yaml.load_all()`.
2016-01-15 18:46:49 +01:00
Adrien Vergé
1934206cef Rules: comments-indentation: Fix typo 2016-01-15 18:46:40 +01:00
Adrien Vergé
1235eba94e yamllint version 0.2.0 2016-01-15 09:39:50 +01:00
Adrien Vergé
11a14d4df8 Distribution: Update program description 2016-01-14 21:08:10 +01:00
Adrien Vergé
233a70adb3 Rules: Add the 'comments-indentation' rule 2016-01-14 21:04:41 +01:00
Adrien Vergé
e81b73c111 Rules: indentation: Rewrite algorithm 2016-01-14 20:57:35 +01:00
Adrien Vergé
3989a09d32 Rules: comments: Allow empty comments 2016-01-14 19:58:35 +01:00
Adrien Vergé
5cc900f2a8 Rules: document-start: Allow directives 2016-01-14 19:58:05 +01:00
Adrien Vergé
851b9ac42c Rules: Add the 'comments' rule 2016-01-14 11:17:01 +01:00
Adrien Vergé
5c4c208b98 Rules: Add the 'braces' rule 2016-01-14 10:46:16 +01:00
Adrien Vergé
d08eb22081 Rules: Add the 'brackets' rule 2016-01-14 10:46:16 +01:00
Adrien Vergé
a5b384ab21 Rules: Add the 'commas' rule 2016-01-14 10:46:16 +01:00
Adrien Vergé
cfea0661ed Rules: Make max-spaces-* generic
The goal being to use them in the 'colons', 'hyphens', 'commas', etc.
rules.
2016-01-14 10:46:16 +01:00
Adrien Vergé
07c5b4177c Rewrite syntax errors handling and test them
If a syntax errors occurs at the same place than a regular yamllint rule
error, only the yamllint one is issued.
2016-01-14 10:46:16 +01:00
Adrien Vergé
bf96bdde01 Tests: Remove assertIsInstance to support Python 2.6 2016-01-14 10:46:16 +01:00
Adrien Vergé
e2d68dac14 Tests: Travis and Coveralls integration 2016-01-14 10:46:16 +01:00
56 changed files with 4221 additions and 422 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
__pycache__
*.py[cod]
/docs/_build

17
.travis.yml Normal file
View File

@@ -0,0 +1,17 @@
---
language: python
python:
- 2.7
- 3.3
- 3.4
- 3.5
- nightly
install:
- pip install pyyaml flake8 coveralls
- pip install .
script:
- flake8 .
- yamllint $(git ls-files '*.yml')
- coverage run --source=yamllint setup.py test
after_success:
coveralls

View File

@@ -1,3 +0,0 @@
.PHONY: tests
tests:
python -m unittest discover

View File

@@ -1 +0,0 @@
# yamllint

65
README.rst Normal file
View File

@@ -0,0 +1,65 @@
yamllint
========
A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc.
.. image::
https://travis-ci.org/adrienverge/yamllint.svg?branch=master
:target: https://travis-ci.org/adrienverge/yamllint
:alt: CI tests status
.. image::
https://coveralls.io/repos/github/adrienverge/yamllint/badge.svg?branch=master
:target: https://coveralls.io/github/adrienverge/yamllint?branch=master
:alt: Code coverage status
.. image:: https://readthedocs.org/projects/yamllint/badge/?version=latest
:target: http://yamllint.readthedocs.org/en/latest/?badge=latest
:alt: Documentation status
Written in Python (compatible with Python 2 & 3).
Documentation
-------------
http://yamllint.readthedocs.org/
Short overview
--------------
Screenshot
^^^^^^^^^^
.. image:: docs/screenshot.png
:alt: yamllint screenshot
Installation
^^^^^^^^^^^^
.. code:: bash
sudo pip install yamllint
Usage
^^^^^
.. code:: bash
# Lint one or more files
yamllint my_file.yml my_other_file.yaml ...
.. code:: bash
# Lint all YAML files in a directory
yamllint .
.. code:: bash
# Use a custom lint configuration
yamllint -c ~/myconfig file.yml
.. code:: bash
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yml

177
docs/Makefile Normal file
View File

@@ -0,0 +1,177 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/yamllint.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/yamllint.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/yamllint"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/yamllint"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

43
docs/conf.py Normal file
View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# yamllint documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 21 21:18:52 2016.
import sys
import os
sys.path.insert(0, os.path.abspath('..')) # noqa
from yamllint import __copyright__, APP_NAME, APP_VERSION
# -- General configuration ------------------------------------------------
extensions = [
'sphinx.ext.autodoc',
]
source_suffix = '.rst'
master_doc = 'index'
project = APP_NAME
copyright = __copyright__
version = APP_VERSION
release = APP_VERSION
pygments_style = 'sphinx'
# -- Options for HTML output ----------------------------------------------
html_theme = 'default'
htmlhelp_basename = 'yamllintdoc'
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'yamllint', u'yamllint Documentation',
[u'Adrien Vergé'], 1)
]

74
docs/configuration.rst Normal file
View File

@@ -0,0 +1,74 @@
Configuration
=============
yamllint uses a set of *rules* to check sources files for problems. Each rule is
independent from the others, and can be enabled, disabled or tweaked. All these
settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working
directory, or use the ``-c`` option:
::
yamllint -c ~/myconfig file.yml
Default configuration
---------------------
Unless told otherwise, yamllint uses its ``default`` configuration:
.. literalinclude:: ../yamllint/conf/default.yml
:language: yaml
Details on rules can be found on :doc:`the rules page <rules>`.
Extending the default configuration
-----------------------------------
When writing a custom configuration file, you don't need to redefine every rule.
Just extend the ``default`` configuration (or any already-existing configuration
file).
For instance, if you just want to disable the ``comments-indentation`` rule,
your file could look like this:
.. code-block:: yaml
# This is my first, very own configuration file for yamllint!
# It extends the default conf by adjusting some options.
extends: default
rules:
comments-indentation: disable # don't bother me with this rule
Similarly, if you want to set the ``line-length`` rule as a warning and be less
strict on block sequences indentation:
.. code-block:: yaml
extends: default
rules:
# 80 should be enough, but don't fail if a line is longer
line-length:
max: 80
level: warning
# accept both key:
# - item
#
# and key:
# - item
indentation:
indent-sequences: whatever
Errors and warnings
-------------------
Problems detected by yamllint can be raised either as errors or as warnings.
In both cases, the script will output them (with different colors when using the
``standard`` output format), but the exit code can be different. More precisely,
the script will exit will a failure code *only when* there is one or more
error(s).

8
docs/development.rst Normal file
View File

@@ -0,0 +1,8 @@
Development
===========
yamllint provides both a script and a Python module. The latter can be used to
write your own linting tools:
.. automodule:: yamllint.linter
:members:

30
docs/index.rst Normal file
View File

@@ -0,0 +1,30 @@
yamllint documentation
======================
A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc.
Screenshot
----------
.. image:: screenshot.png
:alt: yamllint screenshot
.. note::
The default output format is inspired by `eslint <http://eslint.org/>`_, a
great linting tool for Javascript.
Table of contents
-----------------
.. toctree::
:maxdepth: 2
quickstart
configuration
rules
development
text_editors

72
docs/quickstart.rst Normal file
View File

@@ -0,0 +1,72 @@
Quickstart
==========
Installing yamllint
-------------------
First, install yamllint. The easiest way is to use pip, the Python package
manager:
::
sudo pip install yamllint
If you prefer installing from source, you can run, from the source directory:
::
python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz
Running yamllint
----------------
Basic usage:
::
yamllint file.yml other-file.yaml
You can also lint all YAML files in a whole directory:
::
yamllint .
The output will look like (colors are not displayed here):
::
file.yml
6:2 warning missing starting space in comment (comments)
57:1 error trailing spaces (trailing-spaces)
60:3 error wrong indentation: expected 4 but found 2 (indentation)
other-file.yml
1:1 warning missing document start "---" (document-start)
9:81 error line too long (84 > 80 characters) (line-length)
31:1 error too many blank lines (4 > 2) (empty-lines)
37:12 error too many spaces inside braces (braces)
Add the ``-f parsable`` arguments if you need an output format parsable by a
machine (for instance for :doc:`syntax highlighting in text editors
<text_editors>`). The output will then look like:
::
file.yml:6:2: [warning] missing starting space in comment (comments)
file.yml:57:1: [error] trailing spaces (trailing-spaces)
file.yml:60:3: [error] wrong indentation: expected 4 but found 2 (indentation)
If you have a custom linting configuration file (see :doc:`how to configure
yamllint <configuration>`), it can be passed to yamllint using the ``-c``
option:
::
yamllint -c ~/myconfig file.yml
.. note::
If you have a ``.yamllint`` file in your working directory, it will be
automatically loaded as configuration by yamllint.

90
docs/rules.rst Normal file
View File

@@ -0,0 +1,90 @@
Rules
=====
When linting a document with yamllint, a series of rules (such as
``line-length``, ``trailing-spaces``, etc.) are checked against.
A :doc:`configuration file <configuration>` can be used to enable or disable
these rules, to set their level (*error* or *warning*), but also to tweak their
options.
This page describes the rules and their options.
.. contents:: List of rules
:local:
:depth: 1
braces
------
.. automodule:: yamllint.rules.braces
brackets
--------
.. automodule:: yamllint.rules.brackets
colons
------
.. automodule:: yamllint.rules.colons
commas
------
.. automodule:: yamllint.rules.commas
comments
--------
.. automodule:: yamllint.rules.comments
comments-indentation
--------------------
.. automodule:: yamllint.rules.comments_indentation
document-end
------------
.. automodule:: yamllint.rules.document_end
document-start
--------------
.. automodule:: yamllint.rules.document_start
empty-lines
-----------
.. automodule:: yamllint.rules.empty_lines
hyphens
-------
.. automodule:: yamllint.rules.hyphens
indentation
-----------
.. automodule:: yamllint.rules.indentation
line-length
-----------
.. automodule:: yamllint.rules.line_length
new-line-at-end-of-file
-----------------------
.. automodule:: yamllint.rules.new_line_at_end_of_file
new-lines
---------
.. automodule:: yamllint.rules.new_lines
trailing-spaces
---------------
.. automodule:: yamllint.rules.trailing_spaces

BIN
docs/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

40
docs/text_editors.rst Normal file
View File

@@ -0,0 +1,40 @@
Integration with text editors
=============================
Most text editors support syntax checking and highlighting, to visually report
syntax errors and warnings to the user. yamllint can be used to syntax-check
YAML source, but a bit of configuration is required depending on your favorite
text editor.
Vim
---
Assuming that the `syntastic <https://github.com/scrooloose/syntastic>`_ plugin
is installed, add to your ``.vimrc``:
::
TODO
Neovim
------
Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
installed, add to your ``.config/nvim/init.vim``:
::
if executable('yamllint')
let g:neomake_yaml_yamllint_maker = {
\ 'args': ['-f', 'parsable'],
\ 'errorformat': '%E%f:%l:%c: [error] %m,%W%f:%l:%c: [warning] %m' }
let g:neomake_yaml_enabled_makers = ['yamllint']
endif
Other text editors
------------------
.. rubric:: Help wanted!
Your favorite text editor is not listed here? Help us improve by adding a
section (by opening a pull-request or issue on GitHub).

View File

@@ -24,9 +24,10 @@ setup(
name=APP_NAME,
version=APP_VERSION,
author=__author__,
description=APP_DESCRIPTION,
description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION,
license=__license__,
keywords=['yaml', 'lint', 'linter'],
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint',
classifiers=[
'Development Status :: 4 - Beta',
@@ -42,9 +43,9 @@ setup(
],
packages=find_packages(),
scripts=['bin/yamllint'],
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yml']},
install_requires=[
'pyyaml>=3'
],
install_requires=['pyyaml'],
tests_require=['nose'],
test_suite='nose.collector',
)

View File

@@ -18,9 +18,8 @@ import unittest
import yaml
from yamllint.config import parse_config
from yamllint.errors import LintProblem
from yamllint import lint
from yamllint.config import YamlLintConfig
from yamllint import linter
class RuleTestCase(unittest.TestCase):
@@ -31,15 +30,22 @@ class RuleTestCase(unittest.TestCase):
conf = yaml.safe_load(conf)
conf = {'extends': 'default',
'rules': conf}
return parse_config(yaml.safe_dump(conf))
return YamlLintConfig(yaml.safe_dump(conf))
def check(self, source, conf, line=None, column=None, **kwargs):
def check(self, source, conf, **kwargs):
expected_problems = []
for key in kwargs:
assert key.startswith('problem')
expected_problems.append(
LintProblem(kwargs[key][0], kwargs[key][1], rule=self.rule_id))
if len(kwargs[key]) > 2:
if kwargs[key][2] == 'syntax':
rule_id = None
else:
rule_id = kwargs[key][2]
else:
rule_id = self.rule_id
expected_problems.append(linter.LintProblem(
kwargs[key][0], kwargs[key][1], rule=rule_id))
expected_problems.sort()
real_problems = list(lint(source, self.build_fake_config(conf)))
real_problems = list(linter.run(source, self.build_fake_config(conf)))
self.assertEqual(real_problems, expected_problems)

108
tests/rules/test_braces.py Normal file
View File

@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class ColonTestCase(RuleTestCase):
rule_id = 'braces'
def test_disabled(self):
conf = 'braces: disable'
self.check('---\n'
'dict1: {}\n'
'dict2: { }\n'
'dict3: { a: 1, b}\n'
'dict4: {a: 1, b, c: 3 }\n'
'dict5: {a: 1, b, c: 3 }\n'
'dict6: { a: 1, b, c: 3 }\n'
'dict7: { a: 1, b, c: 3 }\n', conf)
def test_min_spaces(self):
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 0}'
self.check('---\n'
'dict: {}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 1}'
self.check('---\n'
'dict: {}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: { }\n', conf)
self.check('---\n'
'dict: {a: 1, b}\n', conf,
problem1=(2, 8), problem2=(2, 15))
self.check('---\n'
'dict: { a: 1, b }\n', conf)
self.check('---\n'
'dict: {\n'
' a: 1,\n'
' b\n'
'}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 3}'
self.check('---\n'
'dict: { a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 17))
self.check('---\n'
'dict: { a: 1, b }\n', conf)
def test_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: -1}'
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: { }\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a: 1, b}\n', conf)
self.check('---\n'
'dict: { a: 1, b }\n', conf,
problem1=(2, 8), problem2=(2, 16))
self.check('---\n'
'dict: { a: 1, b }\n', conf,
problem1=(2, 10), problem2=(2, 20))
self.check('---\n'
'dict: {\n'
' a: 1,\n'
' b\n'
'}\n', conf)
conf = 'braces: {max-spaces-inside: 3, min-spaces-inside: -1}'
self.check('---\n'
'dict: { a: 1, b }\n', conf)
self.check('---\n'
'dict: { a: 1, b }\n', conf,
problem1=(2, 11), problem2=(2, 23))
def test_min_and_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: 0}'
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: { }\n', conf, problem=(2, 8))
self.check('---\n'
'dict: { a: 1, b}\n', conf, problem=(2, 10))
conf = 'braces: {max-spaces-inside: 1, min-spaces-inside: 1}'
self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8))
conf = 'braces: {max-spaces-inside: 2, min-spaces-inside: 0}'
self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf)
self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf)
self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10))

View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class ColonTestCase(RuleTestCase):
rule_id = 'brackets'
def test_disabled(self):
conf = 'brackets: disable'
self.check('---\n'
'array1: []\n'
'array2: [ ]\n'
'array3: [ a, b]\n'
'array4: [a, b, c ]\n'
'array5: [a, b, c ]\n'
'array6: [ a, b, c ]\n'
'array7: [ a, b, c ]\n', conf)
def test_min_spaces(self):
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 0}'
self.check('---\n'
'array: []\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 1}'
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [a, b]\n', conf, problem1=(2, 9), problem2=(2, 13))
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 3}'
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 10), problem2=(2, 15))
self.check('---\n'
'array: [ a, b ]\n', conf)
def test_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: -1}'
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [a, b]\n', conf)
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 9), problem2=(2, 14))
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 11), problem2=(2, 18))
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf)
conf = 'brackets: {max-spaces-inside: 3, min-spaces-inside: -1}'
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 12), problem2=(2, 21))
def test_min_and_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: 0}'
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ a, b]\n', conf, problem=(2, 11))
conf = 'brackets: {max-spaces-inside: 1, min-spaces-inside: 1}'
self.check('---\n'
'array: [a, b, c ]\n', conf, problem=(2, 9))
conf = 'brackets: {max-spaces-inside: 2, min-spaces-inside: 0}'
self.check('---\n'
'array: [a, b, c ]\n', conf)
self.check('---\n'
'array: [ a, b, c ]\n', conf)
self.check('---\n'
'array: [ a, b, c ]\n', conf, problem=(2, 11))

View File

@@ -32,16 +32,16 @@ class ColonTestCase(RuleTestCase):
' val\n'
' property : value\n'
' prop2 : val2\n'
' propriété : [ valeur ]\n'
' propriété : [valeur]\n'
' o:\n'
' k1: [v1, v2]\n'
' p:\n'
' - k3: >\n'
' val\n'
' - o: { k1: v1 }\n'
' - o: {k1: v1}\n'
' - p: kdjf\n'
' - q: val0\n'
' q2:\n'
' - q2:\n'
' - val1\n'
'...\n', conf)
self.check('---\n'
@@ -54,7 +54,7 @@ class ColonTestCase(RuleTestCase):
' val\n'
' property: value\n'
' prop2: val2\n'
' propriété: [ valeur ]\n'
' propriété: [valeur]\n'
' o:\n'
' k1: [v1, v2]\n', conf)
self.check('---\n'
@@ -64,9 +64,9 @@ class ColonTestCase(RuleTestCase):
' val\n'
' - k3: >\n'
' val\n'
' - o: { k1: v1 }\n'
' - o: { k1: v1 }\n'
' q2:\n'
' - o: {k1: v1}\n'
' - o: {k1: v1}\n'
' - q2:\n'
' - val1\n'
'...\n', conf)
self.check('---\n'
@@ -94,7 +94,7 @@ class ColonTestCase(RuleTestCase):
'...\n', conf, problem=(2, 4))
self.check('---\n'
'- lib :\n'
' - var\n'
' - var\n'
'...\n', conf, problem=(2, 6))
self.check('---\n'
'a: {b: {c : d, e : f}}\n', conf,
@@ -118,7 +118,7 @@ class ColonTestCase(RuleTestCase):
'...\n', conf, problem=(3, 8))
def test_before_with_explicit_block_mappings(self):
conf = 'colons: {max-spaces-before: 0, max-spaces-after: -1}'
conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
self.check('---\n'
'object:\n'
' ? key\n'
@@ -129,6 +129,30 @@ class ColonTestCase(RuleTestCase):
' ? key\n'
' : value\n'
'...\n', conf, problem=(2, 7))
self.check('---\n'
'? >\n'
' multi-line\n'
' key\n'
': >\n'
' multi-line\n'
' value\n'
'...\n', conf)
self.check('---\n'
'- ? >\n'
' multi-line\n'
' key\n'
' : >\n'
' multi-line\n'
' value\n'
'...\n', conf)
self.check('---\n'
'- ? >\n'
' multi-line\n'
' key\n'
' : >\n'
' multi-line\n'
' value\n'
'...\n', conf, problem=(5, 5))
def test_after_enabled(self):
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
@@ -152,6 +176,21 @@ class ColonTestCase(RuleTestCase):
'a: {b: {c: d, e : f}}\n', conf,
problem1=(2, 12), problem2=(2, 20))
def test_after_enabled_question_mark(self):
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
self.check('---\n'
'? key\n'
': value\n', conf)
self.check('---\n'
'? key\n'
': value\n', conf, problem=(2, 3))
self.check('---\n'
'? key\n'
': value\n', conf, problem1=(2, 3), problem2=(3, 3))
self.check('---\n'
'- ? key\n'
' : value\n', conf, problem1=(2, 5), problem2=(3, 5))
def test_after_max(self):
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 3}'
self.check('---\n'

187
tests/rules/test_commas.py Normal file
View File

@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class CommaTestCase(RuleTestCase):
rule_id = 'commas'
def test_disabled(self):
conf = 'commas: disable'
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n'
'array: [\n'
' elem ,\n'
' key: val ,\n'
']\n'
'map: {\n'
' key1: val1 ,\n'
' key2: val2,\n'
'}\n'
'...\n', conf)
def test_before_enabled(self):
conf = 'commas: {max-spaces-before: 0, max-spaces-after: -1}'
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf)
self.check('---\n'
'array: [1, 2 , 3, 4]\n'
'...\n', conf, problem=(2, 13))
self.check('---\n'
'array: [1 , 2, 3 , 4]\n'
'...\n', conf, problem1=(2, 10), problem2=(2, 23))
self.check('---\n'
'dict: {a: b, c: "1 2 3", d: e, f: [g, h]}\n'
'...\n', conf)
self.check('---\n'
'dict: {a: b, c: "1 2 3" , d: e, f: [g, h]}\n'
'...\n', conf, problem=(2, 24))
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e, f: [g , h]}\n'
'...\n', conf, problem1=(2, 12), problem2=(2, 42))
self.check('---\n'
'array: [\n'
' elem,\n'
' key: val,\n'
']\n', conf)
self.check('---\n'
'array: [\n'
' elem ,\n'
' key: val,\n'
']\n', conf, problem=(3, 7))
self.check('---\n'
'map: {\n'
' key1: val1,\n'
' key2: val2,\n'
'}\n', conf)
self.check('---\n'
'map: {\n'
' key1: val1,\n'
' key2: val2 ,\n'
'}\n', conf, problem=(4, 13))
def test_before_max(self):
conf = 'commas: {max-spaces-before: 3, max-spaces-after: -1}'
self.check('---\n'
'array: [1 , 2, 3 , 4]\n'
'...\n', conf)
self.check('---\n'
'array: [1 , 2, 3 , 4]\n'
'...\n', conf, problem=(2, 20))
self.check('---\n'
'array: [\n'
' elem1 ,\n'
' elem2 ,\n'
' key: val,\n'
']\n', conf, problem=(4, 11))
def test_after_enabled(self):
conf = 'commas: {max-spaces-before: -1, max-spaces-after: 1}'
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf)
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf, problem=(2, 15))
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf, problem1=(2, 12), problem2=(2, 22))
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
'...\n', conf)
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
'...\n', conf, problem=(2, 27))
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
'...\n', conf, problem1=(2, 15), problem2=(2, 44))
self.check('---\n'
'array: [\n'
' elem,\n'
' key: val,\n'
']\n', conf)
self.check('---\n'
'array: [\n'
' elem, key: val,\n'
']\n', conf, problem=(3, 9))
self.check('---\n'
'map: {\n'
' key1: val1, key2: [val2, val3]\n'
'}\n', conf, problem1=(3, 16), problem2=(3, 30))
def test_after_max(self):
conf = 'commas: {max-spaces-before: -1, max-spaces-after: 3}'
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf)
self.check('---\n'
'array: [1, 2, 3, 4]\n'
'...\n', conf, problem=(2, 21))
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
'...\n', conf, problem1=(2, 31), problem2=(2, 49))
def test_both_before_and_after(self):
conf = 'commas: {max-spaces-before: 0, max-spaces-after: 1}'
self.check('---\n'
'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n'
'array: [\n'
' elem ,\n'
' key: val ,\n'
']\n'
'map: {\n'
' key1: val1 ,\n'
' key2: val2,\n'
'}\n'
'...\n', conf,
problem1=(2, 12), problem2=(2, 16), problem3=(2, 31),
problem4=(2, 36), problem5=(2, 50), problem6=(4, 8),
problem7=(5, 11), problem8=(8, 13))
def test_comma_on_new_line(self):
conf = 'commas: {max-spaces-before: 0, max-spaces-after: 1}'
self.check('---\n'
'flow-seq: [1, 2, 3\n'
' , 4, 5, 6]\n'
'...\n', conf, problem=(3, 11))
self.check('---\n'
'flow-map: {a: 1, b: 2\n'
' , c: 3}\n'
'...\n', conf, problem=(3, 11))
conf = ('commas: {max-spaces-before: 0, max-spaces-after: 1}\n'
'indentation: disable\n')
self.check('---\n'
'flow-seq: [1, 2, 3\n'
' , 4, 5, 6]\n'
'...\n', conf, problem=(3, 9))
self.check('---\n'
'flow-map: {a: 1, b: 2\n'
' , c: 3}\n'
'...\n', conf, problem=(3, 9))
self.check('---\n'
'[\n'
'1,\n'
'2\n'
', 3\n'
']\n', conf, problem=(5, 1))
self.check('---\n'
'{\n'
'a: 1,\n'
'b: 2\n'
', c: 3\n'
'}\n', conf, problem=(5, 1))

View File

@@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class CommentsTestCase(RuleTestCase):
rule_id = 'comments'
def test_disabled(self):
conf = ('comments: disable\n'
'comments-indentation: disable\n')
self.check('---\n'
'#comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello #world\n'
'\n'
'# comment 2\n'
'#comment 3\n'
' #comment 3 bis\n'
' # comment 3 ter\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf)
def test_starting_space(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' min-spaces-from-content: -1\n'
'comments-indentation: disable\n')
self.check('---\n'
'# comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello # world\n'
'\n'
'# comment 2\n'
'# comment 3\n'
' # comment 3 bis\n'
' # comment 3 ter\n', conf)
self.check('---\n'
'#comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello #world\n'
'\n'
'# comment 2\n'
'#comment 3\n'
' #comment 3 bis\n'
' # comment 3 ter\n', conf,
problem1=(2, 2), problem2=(6, 13),
problem4=(9, 2), problem5=(10, 4))
def test_spaces_from_content(self):
conf = ('comments:\n'
' require-starting-space: no\n'
' min-spaces-from-content: 2\n')
self.check('---\n'
'# comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello #world\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf)
self.check('---\n'
'# comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello #world\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf,
problem1=(4, 7), problem2=(6, 11), problem3=(8, 30))
def test_both(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' min-spaces-from-content: 2\n'
'comments-indentation: disable\n')
self.check('---\n'
'#comment\n'
'\n'
'test: # description\n'
' - foo # bar\n'
' - hello #world\n'
'\n'
'# comment 2\n'
'#comment 3\n'
' #comment 3 bis\n'
' # comment 3 ter\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf,
problem1=(2, 2),
problem2=(4, 7),
problem3=(6, 11), problem4=(6, 12),
problem5=(9, 2),
problem6=(10, 4),
problem7=(13, 30))
def test_empty_comment(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' min-spaces-from-content: 2\n')
self.check('---\n'
'# This is paragraph 1.\n'
'#\n'
'# This is paragraph 2.\n', conf)
self.check('---\n'
'inline: comment #\n'
'foo: bar\n', conf)
def test_first_line(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' min-spaces-from-content: 2\n')
self.check('# comment\n', conf)
def test_multi_line_scalar(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' min-spaces-from-content: 2\n'
'trailing-spaces: disable\n')
self.check('---\n'
'string: >\n'
' this is plain text\n'
'\n'
'# comment\n', conf)
self.check('---\n'
'- string: >\n'
' this is plain text\n'
' \n'
' # comment\n', conf)

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class CommentsIndentationTestCase(RuleTestCase):
rule_id = 'comments-indentation'
def test_disable(self):
conf = 'comments-indentation: disable'
self.check('---\n'
' # line 1\n'
'# line 2\n'
' # line 3\n'
' # line 4\n'
'\n'
'obj:\n'
' # these\n'
' # are\n'
' # [good]\n'
'# bad\n'
' # comments\n'
' a: b\n'
'\n'
'obj1:\n'
' a: 1\n'
' # comments\n'
'\n'
'obj2:\n'
' b: 2\n'
'\n'
'# empty\n'
'#\n'
'# comment\n'
'...\n', conf)
def test_enabled(self):
conf = 'comments-indentation: {}'
self.check('---\n'
'# line 1\n'
'# line 2\n', conf)
self.check('---\n'
' # line 1\n'
'# line 2\n', conf, problem=(2, 2))
self.check('---\n'
' # line 1\n'
' # line 2\n', conf, problem1=(2, 3), problem2=(3, 3))
self.check('---\n'
'obj:\n'
' # normal\n'
' a: b\n', conf)
self.check('---\n'
'obj:\n'
' # bad\n'
' a: b\n', conf, problem=(3, 2))
self.check('---\n'
'obj:\n'
'# bad\n'
' a: b\n', conf, problem=(3, 1))
self.check('---\n'
'obj:\n'
' # bad\n'
' a: b\n', conf, problem=(3, 4))
self.check('---\n'
'obj:\n'
' # these\n'
' # are\n'
' # [good]\n'
'# bad\n'
' # comments\n'
' a: b\n', conf,
problem1=(3, 2), problem2=(4, 4),
problem3=(6, 1), problem4=(7, 7))
self.check('---\n'
'obj1:\n'
' a: 1\n'
' # the following line is disabled\n'
' # b: 2\n', conf)
self.check('---\n'
'obj1:\n'
' a: 1\n'
' # b: 2\n'
'\n'
'obj2:\n'
' b: 2\n', conf)
self.check('---\n'
'obj1:\n'
' a: 1\n'
' # b: 2\n'
'# this object is useless\n'
'obj2: no\n', conf)
self.check('---\n'
'obj1:\n'
' a: 1\n'
'# this object is useless\n'
' # b: 2\n'
'obj2: no\n', conf, problem=(5, 3))
self.check('---\n'
'obj1:\n'
' a: 1\n'
' # comments\n'
' b: 2\n', conf)
self.check('---\n'
'my list for today:\n'
' - todo 1\n'
' - todo 2\n'
' # commented for now\n'
' # - todo 3\n'
'...\n', conf)
def test_first_line(self):
conf = 'comments-indentation: {}'
self.check('# comment\n', conf)
self.check(' # comment\n', conf, problem=(1, 3))
def test_no_newline_at_end(self):
conf = ('comments-indentation: {}\n'
'new-line-at-end-of-file: disable\n')
self.check('# comment', conf)
self.check(' # comment', conf, problem=(1, 3))
def test_empty_comment(self):
conf = 'comments-indentation: {}'
self.check('---\n'
'# hey\n'
'# normal\n'
'#\n', conf)
self.check('---\n'
'# hey\n'
'# normal\n'
' #\n', conf, problem=(4, 2))

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import yaml
from yamllint.rules.common import (Comment, get_line_indent,
get_comments_between_tokens)
class CommonTestCase(unittest.TestCase):
def test_get_line_indent(self):
tokens = list(yaml.scan('a: 1\n'
'b:\n'
' - c: [2, 3, {d: 4}]\n'))
self.assertEqual(tokens[3].value, 'a')
self.assertEqual(tokens[5].value, '1')
self.assertEqual(tokens[7].value, 'b')
self.assertEqual(tokens[13].value, 'c')
self.assertEqual(tokens[16].value, '2')
self.assertEqual(tokens[18].value, '3')
self.assertEqual(tokens[22].value, 'd')
self.assertEqual(tokens[24].value, '4')
for i in (3, 5):
self.assertEqual(get_line_indent(tokens[i]), 0)
for i in (7,):
self.assertEqual(get_line_indent(tokens[i]), 0)
for i in (13, 16, 18, 22, 24):
self.assertEqual(get_line_indent(tokens[i]), 2)
def check_comments(self, buffer, *expected):
yaml_loader = yaml.BaseLoader(buffer)
comments = []
next = yaml_loader.peek_token()
while next is not None:
curr = yaml_loader.get_token()
next = yaml_loader.peek_token()
for comment in get_comments_between_tokens(curr, next):
comments.append(comment)
self.assertEqual(comments, list(expected))
def test_get_comments_between_tokens(self):
self.check_comments('# comment\n',
Comment(1, 1, '# comment', 0))
self.check_comments('---\n'
'# comment\n'
'...\n',
Comment(2, 1, '# comment', 0))
self.check_comments('---\n'
'# no newline char',
Comment(2, 1, '# no newline char', 0))
self.check_comments('# just comment',
Comment(1, 1, '# just comment', 0))
self.check_comments('\n'
' # indented comment\n',
Comment(2, 4, '# indented comment', 0))
self.check_comments('\n'
'# trailing spaces \n',
Comment(2, 1, '# trailing spaces ', 0))
self.check_comments('# comment one\n'
'\n'
'key: val # key=val\n'
'\n'
'# this is\n'
'# a block \n'
'# comment\n'
'\n'
'other:\n'
' - foo # equals\n'
' # bar\n',
Comment(1, 1, '# comment one', 0),
Comment(3, 11, '# key=val', 0),
Comment(5, 1, '# this is', 0),
Comment(6, 1, '# a block ', 0),
Comment(7, 1, '# comment', 0),
Comment(10, 10, '# equals', 0),
Comment(11, 10, '# bar', 0))

View File

@@ -64,12 +64,6 @@ class DocumentEndTestCase(RuleTestCase):
'---\n'
'third: document\n'
'...\n', conf)
self.check('first: document\n'
'...\n'
'second: document\n'
'...\n'
'third: document\n'
'...\n', conf)
self.check('---\n'
'first: document\n'
'...\n'

View File

@@ -82,9 +82,23 @@ class DocumentStartTestCase(RuleTestCase):
'...\n'
'second: document\n'
'---\n'
'third: document\n', conf, problem=(4, 1))
'third: document\n', conf, problem=(4, 1, 'syntax'))
def test_directives(self):
# TODO
# %YAML 1.2
pass
conf = 'document-start: {present: yes}'
self.check('%YAML 1.2\n'
'---\n'
'doc: ument\n'
'...\n', conf)
self.check('%YAML 1.2\n'
'%TAG ! tag:clarkevans.com,2002:\n'
'---\n'
'doc: ument\n'
'...\n', conf)
self.check('---\n'
'doc: 1\n'
'...\n'
'%YAML 1.2\n'
'---\n'
'doc: 2\n'
'...\n', conf)

View File

@@ -36,12 +36,12 @@ class HyphenTestCase(RuleTestCase):
'- elem2\n', conf)
self.check('---\n'
'object:\n'
'- elem1\n'
'- elem2\n', conf)
' - elem1\n'
' - elem2\n', conf)
self.check('---\n'
'object:\n'
'- elem1\n'
'- elem2\n', conf)
' - elem1\n'
' - elem2\n', conf)
self.check('---\n'
'object:\n'
' subobject:\n'
@@ -69,12 +69,12 @@ class HyphenTestCase(RuleTestCase):
'- elem2\n', conf, problem=(2, 3))
self.check('---\n'
'object:\n'
'- elem1\n'
'- elem2\n', conf, problem=(4, 3))
' - elem1\n'
' - elem2\n', conf, problem=(4, 5))
self.check('---\n'
'object:\n'
'- elem1\n'
'- elem2\n', conf, problem1=(3, 3), problem2=(4, 3))
' - elem1\n'
' - elem2\n', conf, problem1=(3, 5), problem2=(4, 5))
self.check('---\n'
'object:\n'
' subobject:\n'

View File

@@ -48,37 +48,205 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf)
def test_one_space(self):
conf = 'indentation: {spaces: 1}'
conf = 'indentation: {spaces: 1, indent-sequences: no}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
conf = 'indentation: {spaces: 1, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
def test_two_spaces(self):
conf = 'indentation: {spaces: 2}'
conf = 'indentation: {spaces: 2, indent-sequences: no}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
def test_three_spaces(self):
conf = 'indentation: {spaces: 3}'
conf = 'indentation: {spaces: 3, indent-sequences: no}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
conf = 'indentation: {spaces: 3, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' k1:\n'
' - a\n'
' - b\n'
' k2: v2\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
def test_under_indented(self):
def test_indent_sequences_whatever(self):
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem=(3, 3))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem=(7, 3))
self.check('---\n'
'list:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'- a\n'
'- b\n'
'- c\n', conf, problem=(6, 1, 'syntax'))
def test_flow_mappings(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'a: {x: 1,\n'
' y,\n'
' z: 1}\n', conf)
self.check('---\n'
'a: {x: 1,\n'
' y,\n'
' z: 1}\n', conf, problem=(3, 4))
self.check('---\n'
'a: {x: 1,\n'
' y,\n'
' z: 1}\n', conf, problem=(3, 6))
self.check('---\n'
'a: {x: 1,\n'
' y, z: 1\n'
'}\n', conf, problem=(3, 3))
self.check('---\n'
'a: {\n'
' x: 1,\n'
' y, z: 1\n'
'}\n', conf)
self.check('---\n'
'a: {\n'
' x: 1,\n'
' y, z: 1}\n', conf)
self.check('---\n'
'a: {\n'
' x: 1,\n'
' y, z: 1\n'
'}\n', conf, problem=(3, 4))
self.check('---\n'
'a: {\n'
' x: 1,\n'
' y, z: 1\n'
' }\n', conf, problem=(5, 3))
def test_flow_sequences(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'a: [x,\n'
' y,\n'
' z]\n', conf)
self.check('---\n'
'a: [x,\n'
' y,\n'
' z]\n', conf, problem=(3, 4))
self.check('---\n'
'a: [x,\n'
' y,\n'
' z]\n', conf, problem=(3, 6))
self.check('---\n'
'a: [x,\n'
' y, z\n'
']\n', conf, problem=(3, 3))
self.check('---\n'
'a: [\n'
' x,\n'
' y, z\n'
']\n', conf)
self.check('---\n'
'a: [\n'
' x,\n'
' y, z]\n', conf)
self.check('---\n'
'a: [\n'
' x,\n'
' y, z\n'
']\n', conf, problem=(3, 4))
self.check('---\n'
'a: [\n'
' x,\n'
' y, z\n'
' ]\n', conf, problem=(5, 3))
def test_under_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' val: 1\n'
@@ -88,7 +256,13 @@ class IndentationTestCase(RuleTestCase):
' k1:\n'
' - a\n'
'...\n', conf, problem=(4, 4))
conf = 'indentation: {spaces: 4}'
self.check('---\n'
'object:\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
'...\n', conf, problem=(5, 6, 'syntax'))
conf = 'indentation: {spaces: 4, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' val: 1\n'
@@ -98,9 +272,15 @@ class IndentationTestCase(RuleTestCase):
'- el2:\n'
' - subel\n'
'...\n', conf, problem=(4, 4))
self.check('---\n'
'object:\n'
' k3:\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf, problem=(5, 10, 'syntax'))
def test_over_indented(self):
conf = 'indentation: {spaces: 2}'
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' val: 1\n'
@@ -110,7 +290,13 @@ class IndentationTestCase(RuleTestCase):
' k1:\n'
' - a\n'
'...\n', conf, problem=(4, 6))
conf = 'indentation: {spaces: 4}'
self.check('---\n'
'object:\n'
' k3:\n'
' - name: Unix\n'
' date: 1969\n'
'...\n', conf, problem=(5, 12, 'syntax'))
conf = 'indentation: {spaces: 4, indent-sequences: yes}'
self.check('---\n'
'object:\n'
' val: 1\n'
@@ -132,42 +318,99 @@ class IndentationTestCase(RuleTestCase):
self.check('---\n'
' - el1\n'
' - el2:\n'
' - subel\n'
'...\n', conf, problem1=(2, 3), problem2=(4, 7))
' - subel\n'
'...\n', conf,
problem=(2, 3))
self.check('---\n'
'object:\n'
' k3:\n'
' - name: Linux\n'
' date: 1991\n'
'...\n', conf, problem=(5, 16, 'syntax'))
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n'
' - el1\n'
' - el2:\n'
' - subel\n'
'...\n', conf,
problem=(2, 3))
def test_multi_lines(self):
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
self.check('---\n'
'long_string: >\n'
' bla bla blah\n'
' blah bla bla\n'
'...\n', None)
'...\n', conf)
self.check('---\n'
'- long_string: >\n'
' bla bla blah\n'
' blah bla bla\n'
'...\n', None)
'...\n', conf)
self.check('---\n'
'obj:\n'
' - long_string: >\n'
' bla bla blah\n'
' blah bla bla\n'
'...\n', None)
'...\n', conf)
def test_empty_value(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'key1:\n'
'key2: not empty\n'
'key3:\n'
'...\n', conf)
self.check('---\n'
'-\n'
'- item 2\n'
'-\n'
'...\n', conf)
def test_nested_collections(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', None)
' k1: v1\n'
'...\n', conf)
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', conf, problem=(3, 2, 'syntax'))
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', None, problem=(3, 4))
'...\n', conf, problem=(3, 4))
conf = 'indentation: {spaces: 4}'
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', conf)
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', None, problem=(3, 6))
'...\n', conf, problem=(3, 6))
self.check('---\n'
'- o:\n'
' k1: v1\n'
'...\n', conf, problem=(3, 8))
self.check('---\n'
'- - - - item\n'
' - elem 1\n'
' - elem 2\n'
' - - - - - very nested: a\n'
' key: value\n'
'...\n', conf)
self.check('---\n'
' - - - - item\n'
' - elem 1\n'
' - elem 2\n'
' - - - - - very nested: a\n'
' key: value\n'
'...\n', conf, problem=(2, 2))
def test_return(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'a:\n'
' b:\n'
@@ -176,16 +419,453 @@ class IndentationTestCase(RuleTestCase):
' e:\n'
' f:\n'
'g:\n'
'...\n', None)
# self.check('---\n'
# 'a:\n'
# ' b:\n'
# ' c:\n'
# ' d:\n'
# '...\n', None, problem=(5, 5))
# self.check('---\n'
# 'a:\n'
# ' b:\n'
# ' c:\n'
# ' d:\n'
# '...\n', None, problem=(5, 2))
'...\n', conf)
self.check('---\n'
'a:\n'
' b:\n'
' c:\n'
' d:\n'
'...\n', conf, problem=(5, 4, 'syntax'))
self.check('---\n'
'a:\n'
' b:\n'
' c:\n'
' d:\n'
'...\n', conf, problem=(5, 2, 'syntax'))
def test_first_line(self):
conf = ('indentation: {spaces: 2}\n'
'document-start: disable\n')
self.check(' a: 1\n', conf, problem=(1, 3))
def test_broken_inline_flows(self):
conf = 'indentation: {spaces: 2}'
self.check('---\n'
'obj: {\n'
' a: 1,\n'
' b: 2,\n'
' c: 3\n'
'}\n', conf, problem1=(4, 4), problem2=(5, 2))
self.check('---\n'
'list: [\n'
' 1,\n'
' 2,\n'
' 3\n'
']\n', conf, problem1=(4, 4), problem2=(5, 2))
def test_explicit_block_mappings(self):
conf = 'indentation: {spaces: 4}'
self.check('---\n'
'object:\n'
' ? key\n'
' :\n'
' value\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' ? key\n'
' :\n'
' value\n'
'...\n', conf, problem=(5, 8))
self.check('---\n'
'object:\n'
' ?\n'
' key\n'
' :\n'
' value\n'
'...\n', conf)
self.check('---\n'
'object:\n'
' ?\n'
' key\n'
' :\n'
' value\n'
'...\n', conf, problem1=(4, 8), problem2=(6, 10))
self.check('---\n'
'object:\n'
' ?\n'
' key\n'
' :\n'
' value\n'
'...\n', conf, problem1=(4, 10), problem2=(6, 8))
class ScalarIndentationTestCase(RuleTestCase):
rule_id = 'indentation'
def test_basics_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n'
'document-start: disable\n')
self.check('multi\n'
'line\n', conf)
self.check('multi\n'
' line\n', conf)
self.check('- multi\n'
' line\n', conf)
self.check('- multi\n'
' line\n', conf)
self.check('a key: multi\n'
' line\n', conf)
self.check('a key: multi\n'
' line\n', conf)
self.check('a key: multi\n'
' line\n', conf)
self.check('a key:\n'
' multi\n'
' line\n', conf)
self.check('- C code: void main() {\n'
' printf("foo");\n'
' }\n', conf)
self.check('- C code:\n'
' void main() {\n'
' printf("foo");\n'
' }\n', conf)
def test_check_multi_line_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('multi\n'
' line\n', conf, problem=(2, 2))
self.check('- multi\n'
' line\n', conf, problem=(2, 4))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 3))
self.check('a key: multi\n'
' line\n', conf, problem=(2, 9))
self.check('a key:\n'
' multi\n'
' line\n', conf, problem=(3, 4))
self.check('- C code: void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(2, 15))
self.check('- C code:\n'
' void main() {\n'
' printf("foo");\n'
' }\n', conf, problem=(3, 9))
def test_basics_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n'
'document-start: disable\n')
self.check('"multi\n'
' line"\n', conf)
self.check('- "multi\n'
' line"\n', conf)
self.check('a key: "multi\n'
' line"\n', conf)
self.check('a key:\n'
' "multi\n'
' line"\n', conf)
self.check('- jinja2: "{% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}"\n', conf)
self.check('- jinja2:\n'
' "{% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}"\n', conf)
def test_check_multi_line_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('"multi\n'
'line"\n', conf, problem=(2, 1))
self.check('"multi\n'
' line"\n', conf, problem=(2, 3))
self.check('- "multi\n'
' line"\n', conf, problem=(2, 3))
self.check('- "multi\n'
' line"\n', conf, problem=(2, 5))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 3))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 8))
self.check('a key: "multi\n'
' line"\n', conf, problem=(2, 10))
self.check('a key:\n'
' "multi\n'
' line"\n', conf, problem=(3, 3))
self.check('a key:\n'
' "multi\n'
' line"\n', conf, problem=(3, 5))
self.check('- jinja2: "{% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}"\n', conf,
problem1=(2, 14), problem2=(4, 14))
self.check('- jinja2:\n'
' "{% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}"\n', conf,
problem1=(3, 8), problem2=(5, 8))
def test_basics_folded_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n'
'document-start: disable\n')
self.check('>\n'
' multi\n'
' line\n', conf)
self.check('- >\n'
' multi\n'
' line\n', conf)
self.check('- key: >\n'
' multi\n'
' line\n', conf)
self.check('- key:\n'
' >\n'
' multi\n'
' line\n', conf)
self.check('- ? >\n'
' multi-line\n'
' key\n'
' : >\n'
' multi-line\n'
' value\n', conf)
self.check('- ?\n'
' >\n'
' multi-line\n'
' key\n'
' :\n'
' >\n'
' multi-line\n'
' value\n', conf)
self.check('- jinja2: >\n'
' {% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}\n', conf)
def test_check_multi_line_folded_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('>\n'
' multi\n'
' line\n', conf, problem=(3, 4))
self.check('- >\n'
' multi\n'
' line\n', conf, problem=(3, 6))
self.check('- key: >\n'
' multi\n'
' line\n', conf, problem=(3, 6))
self.check('- key:\n'
' >\n'
' multi\n'
' line\n', conf, problem=(4, 8))
self.check('- ? >\n'
' multi-line\n'
' key\n'
' : >\n'
' multi-line\n'
' value\n', conf,
problem1=(3, 8), problem2=(6, 8))
self.check('- ?\n'
' >\n'
' multi-line\n'
' key\n'
' :\n'
' >\n'
' multi-line\n'
' value\n', conf,
problem1=(4, 8), problem2=(8, 8))
self.check('- jinja2: >\n'
' {% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}\n', conf,
problem1=(3, 7), problem2=(5, 7))
def test_basics_literal_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: no}\n'
'document-start: disable\n')
self.check('|\n'
' multi\n'
' line\n', conf)
self.check('- |\n'
' multi\n'
' line\n', conf)
self.check('- key: |\n'
' multi\n'
' line\n', conf)
self.check('- key:\n'
' |\n'
' multi\n'
' line\n', conf)
self.check('- ? |\n'
' multi-line\n'
' key\n'
' : |\n'
' multi-line\n'
' value\n', conf)
self.check('- ?\n'
' |\n'
' multi-line\n'
' key\n'
' :\n'
' |\n'
' multi-line\n'
' value\n', conf)
self.check('- jinja2: |\n'
' {% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}\n', conf)
def test_check_multi_line_literal_style(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('|\n'
' multi\n'
' line\n', conf, problem=(3, 4))
self.check('- |\n'
' multi\n'
' line\n', conf, problem=(3, 6))
self.check('- key: |\n'
' multi\n'
' line\n', conf, problem=(3, 6))
self.check('- key:\n'
' |\n'
' multi\n'
' line\n', conf, problem=(4, 8))
self.check('- ? |\n'
' multi-line\n'
' key\n'
' : |\n'
' multi-line\n'
' value\n', conf,
problem1=(3, 8), problem2=(6, 8))
self.check('- ?\n'
' |\n'
' multi-line\n'
' key\n'
' :\n'
' |\n'
' multi-line\n'
' value\n', conf,
problem1=(4, 8), problem2=(8, 8))
self.check('- jinja2: |\n'
' {% if ansible is defined %}\n'
' {{ ansible }}\n'
' {% else %}\n'
' {{ chef }}\n'
' {% endif %}\n', conf,
problem1=(3, 7), problem2=(5, 7))
# The following "paragraph" examples are inspired from
# http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines
def test_paragraph_plain(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('- long text: very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf)
self.check('- long text: very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf,
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
self.check('- long text:\n'
' very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf)
def test_paragraph_double_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('- long text: "very \\"long\\"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces."\n', conf)
self.check('- long text: "very \\"long\\"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces."\n', conf,
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
self.check('- long text: "very \\"long\\"\n'
'\'string\' with\n'
'\n'
'paragraph gap, \\n and\n'
'spaces."\n', conf,
problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
self.check('- long text:\n'
' "very \\"long\\"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces."\n', conf)
def test_paragraph_single_quoted(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('- long text: \'very "long"\n'
' \'\'string\'\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\'\n', conf)
self.check('- long text: \'very "long"\n'
' \'\'string\'\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\'\n', conf,
problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
self.check('- long text: \'very "long"\n'
'\'\'string\'\' with\n'
'\n'
'paragraph gap, \\n and\n'
'spaces.\'\n', conf,
problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
self.check('- long text:\n'
' \'very "long"\n'
' \'\'string\'\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\'\n', conf)
def test_paragraph_folded(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('- long text: >\n'
' very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf)
self.check('- long text: >\n'
' very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf,
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
def test_paragraph_literal(self):
conf = ('indentation: {spaces: 2, check-multi-line-strings: yes}\n'
'document-start: disable\n')
self.check('- long text: |\n'
' very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf)
self.check('- long text: |\n'
' very "long"\n'
' \'string\' with\n'
'\n'
' paragraph gap, \\n and\n'
' spaces.\n', conf,
problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))

View File

@@ -43,17 +43,17 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n', conf)
self.check(80 * 'a', conf)
self.check('---\n' + 80 * 'a' + '\n', conf)
self.check(81 * 'a', conf, problem=(1, 81))
self.check('---\n' + 81 * 'a' + '\n', conf, problem=(2, 81))
self.check(1000 * 'b', conf, problem=(1, 81))
self.check('---\n' + 1000 * 'b' + '\n', conf, problem=(2, 81))
self.check(16 * 'aaaa ' + 'z', conf, problem=(1, 81))
self.check('---\n' + 16 * 'aaaa ' + 'z' + '\n', conf, problem=(2, 81))
self.check(1000 * 'word ' + 'end', conf, problem=(1, 81))
self.check('---\n' + 1000 * 'word ' + 'end\n', conf, problem=(2, 81))
def test_max_length_10(self):
conf = ('line-length: {max: 10}\n'
'new-line-at-end-of-file: disable\n')
self.check('---\nABCDEFGHIJ', conf)
self.check('---\nABCDEFGHIJK', conf, problem=(2, 11))
self.check('---\nABCDEFGHIJK\n', conf, problem=(2, 11))
self.check('---\nABCD EFGHI', conf)
self.check('---\nABCD EFGHIJ', conf, problem=(2, 11))
self.check('---\nABCD EFGHIJ\n', conf, problem=(2, 11))
def test_spaces(self):
conf = ('line-length: {max: 80}\n'
@@ -61,3 +61,36 @@ class LineLengthTestCase(RuleTestCase):
'trailing-spaces: disable\n')
self.check('---\n' + 81 * ' ', conf, problem=(2, 81))
self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81))
def test_non_breakable_word(self):
conf = 'line-length: {max: 20, allow-non-breakable-words: yes}'
self.check('---\n' + 30 * 'A' + '\n', conf)
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' http://localhost/very/long/url\n'
'...\n', conf)
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf)
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' http://localhost/very/long/url\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'this:\n'
' is:\n'
' - a:\n'
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf, problem=(5, 21))

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.rules.common import RuleTestCase
class YamlLintTestCase(RuleTestCase):
rule_id = None # syntax error
def test_lint(self):
self.check('---\n'
'this is not: valid: YAML\n', None, problem=(2, 19))
self.check('---\n'
'this is: valid YAML\n'
'\n'
'this is an error: [\n'
'\n'
'...\n', None, problem=(6, 1))
def test_directives(self):
self.check('%YAML 1.2\n'
'%TAG ! tag:clarkevans.com,2002:\n'
'doc: ument\n'
'...\n', None, problem=(3, 1))
def test_explicit_mapping(self):
self.check('---\n'
'? key\n'
': - value 1\n'
' - value 2\n'
'...\n', None)
self.check('---\n'
'?\n'
' key\n'
': {a: 1}\n'
'...\n', None)
self.check('---\n'
'?\n'
' key\n'
':\n'
' val\n'
'...\n', None)
def test_mapping_between_sequences(self):
# This is valid YAML. See http://www.yaml.org/spec/1.2/spec.html,
# example 2.11
self.check('---\n'
'? - Detroit Tigers\n'
' - Chicago cubs\n'
':\n'
' - 2001-07-23\n'
'\n'
'? [New York Yankees,\n'
' Atlanta Braves]\n'
': [2001-07-02, 2001-08-12,\n'
' 2001-08-14]\n', None)

View File

@@ -33,11 +33,11 @@ class TrailingSpacesTestCase(RuleTestCase):
self.check('', conf)
self.check('\n', conf)
self.check(' \n', conf, problem=(1, 1))
self.check('\t\t\t\n', conf, problem=(1, 1))
self.check('\t\t\t\n', conf, problem=(1, 1, 'syntax'))
self.check('---\n'
'some: text \n', conf, problem=(2, 11))
self.check('---\n'
'some: text\t\n', conf, problem=(2, 11))
'some: text\t\n', conf, problem=(2, 11, 'syntax'))
def test_with_dos_new_lines(self):
conf = ('trailing-spaces: {}\n'

177
tests/test_config.py Normal file
View File

@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from yamllint import config
class SimpleConfigTestCase(unittest.TestCase):
def test_parse_config(self):
new = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
self.assertEqual(list(new.rules.keys()), ['colons'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(len(new.enabled_rules()), 1)
def test_unknown_rule(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n')
def test_missing_option(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: missing option "max-spaces-before" '
'for rule "colons"'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-after: 1\n')
def test_unknown_option(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: unknown option "abcdef" for rule "colons"'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' abcdef: yes\n')
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
new = config.YamlLintConfig('rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons: disable\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1)
def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 3)
self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens: disable\n')
new = config.YamlLintConfig('rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
def test_extend_config_disable_rule(self):
old = config.YamlLintConfig('extends: default')
new = config.YamlLintConfig('extends: default\n'
'rules:\n'
' trailing-spaces: disable\n')
old.rules['trailing-spaces'] = False
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule])
def test_extend_config_override_whole_rule(self):
old = config.YamlLintConfig('extends: default')
new = config.YamlLintConfig('extends: default\n'
'rules:\n'
' empty-lines:\n'
' max: 42\n'
' max-start: 43\n'
' max-end: 44\n')
old.rules['empty-lines']['max'] = 42
old.rules['empty-lines']['max-start'] = 43
old.rules['empty-lines']['max-end'] = 44
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule])
def test_extend_config_override_rule_partly(self):
old = config.YamlLintConfig('extends: default')
new = config.YamlLintConfig('extends: default\n'
'rules:\n'
' empty-lines:\n'
' max-start: 42\n')
old.rules['empty-lines']['max-start'] = 42
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule])

View File

@@ -14,67 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint import config
from yamllint import parser
APP_NAME = 'yamllint'
APP_VERSION = '0.1.0'
APP_DESCRIPTION = 'Lint YAML files.'
APP_VERSION = '0.6.0'
APP_DESCRIPTION = """A linter for YAML files.
__author__ = 'Adrien Vergé'
__copyright__ = 'Copyright 2016, Adrien Vergé'
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc."""
__author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2016, Adrien Vergé'
__license__ = 'GPLv3'
__version__ = APP_VERSION
def _lint(buffer, conf):
rules = config.get_enabled_rules(conf)
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
line_rules = [r for r in rules if r.TYPE == 'line']
# If the document contains a syntax error, save it and yield it at the
# right line
syntax_error = parser.get_syntax_error(buffer)
for elem in parser.token_or_line_generator(buffer):
if syntax_error and syntax_error.line <= elem.line_no:
syntax_error.level = 'error'
yield syntax_error
syntax_error = None
if isinstance(elem, parser.Token):
for rule in token_rules:
rule_conf = conf[rule.ID]
for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
elif isinstance(elem, parser.Line):
for rule in line_rules:
rule_conf = conf[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
def lint(input, conf):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
return _lint(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly
content = input.read()
return _lint(content, conf)
else:
raise TypeError('input should be a string or a stream')

76
bin/yamllint → yamllint/cli.py Executable file → Normal file
View File

@@ -22,16 +22,50 @@ import sys
import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint import config
from yamllint.errors import YamlLintConfigError
from yamllint import lint
from yamllint import output
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint import linter
if __name__ == '__main__':
def find_files_recursively(items):
for item in items:
if os.path.isdir(item):
for root, dirnames, filenames in os.walk(item):
for filename in [f for f in filenames
if f.endswith(('.yml', '.yaml'))]:
yield os.path.join(root, filename)
else:
yield item
class Format(object):
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
{'file': filename,
'line': problem.line,
'column': problem.column,
'level': problem.level,
'message': problem.message})
@staticmethod
def standard(problem, filename):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' '
if problem.level == 'warning':
line += '\033[33m%s\033[0m' % problem.level
else:
line += '\033[31m%s\033[0m' % problem.level
line += max(38 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' \033[2m(%s)\033[0m' % problem.rule
return line
def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILES', nargs='+',
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check')
parser.add_argument('-c', '--config', dest='config_file', action='store',
help='path to a custom configuration')
@@ -43,40 +77,42 @@ if __name__ == '__main__':
# TODO: read from stdin when no filename?
args = parser.parse_args()
args = parser.parse_args(argv)
try:
if args.config_file is not None:
conf = config.parse_config_from_file(args.config_file)
conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'):
conf = config.parse_config_from_file('.yamllint')
conf = YamlLintConfig(file='.yamllint')
else:
conf = config.parse_config('extends: default')
conf = YamlLintConfig('extends: default')
except YamlLintConfigError as e:
print(e, file=sys.stderr)
sys.exit(-1)
return_code = 0
for file in args.files:
if args.format != 'parsable':
print('\033[4m%s\033[0m' % file)
for file in find_files_recursively(args.files):
try:
first = True
with open(file) as f:
for problem in lint(f, conf):
for problem in linter.run(f, conf):
if args.format == 'parsable':
print(output.parsable_format(problem, file))
print(Format.parsable(problem, file))
else:
print(output.standard_format(problem, file))
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard(problem, file))
if return_code == 0 and problem.level == 'error':
return_code = 1
if not first and args.format != 'parsable':
print('')
except EnvironmentError as e:
print(e)
return_code = -1
if args.format != 'parsable':
print('')
sys.exit(return_code)

View File

@@ -1,17 +1,24 @@
---
rules:
#block-sequence-indentation:
# present: yes
braces:
min-spaces-inside: 0
max-spaces-inside: 0
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
colons:
max-spaces-before: 0
max-spaces-after: 1
#comma:
# max-spaces-before: 0
# max-spaces-after: 1
#comment:
# min-spaces-after: 1
# min-spaces-before: 2
commas:
max-spaces-before: 0
max-spaces-after: 1
comments:
level: warning
require-starting-space: yes
min-spaces-from-content: 2
comments-indentation:
level: warning
document-end: disable
document-start:
level: warning
@@ -24,16 +31,12 @@ rules:
max-spaces-after: 1
indentation:
spaces: 2
#comments: yes
indent-sequences: yes
check-multi-line-strings: no
line-length:
max: 80
allow-non-breakable-words: yes
new-line-at-end-of-file: {level: error}
new-lines:
type: unix
#spaces-in-brackets: [ 1, 2 ]
# min: 1
# max: 1
#spaces-in-braces: { df: d }
# min: 1
# max: 1
trailing-spaces: {}

View File

@@ -19,10 +19,109 @@ import os.path
import yaml
import yamllint.rules
from yamllint.errors import YamlLintConfigError
def get_extended_conf(name):
class YamlLintConfigError(Exception):
pass
class YamlLintConfig(object):
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
if file is not None:
with open(file) as f:
content = f.read()
self.parse(content)
self.validate()
def enabled_rules(self):
return [yamllint.rules.get(id) for id, val in self.rules.items()
if val is not False]
def extend(self, base_config):
assert isinstance(base_config, YamlLintConfig)
for rule in self.rules:
if (type(self.rules[rule]) == dict and
rule in base_config.rules and
base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule])
else:
base_config.rules[rule] = self.rules[rule]
self.rules = base_config.rules
def parse(self, raw_content):
try:
conf = yaml.safe_load(raw_content)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
self.rules = conf.get('rules', {})
# Does this conf override another conf that we need to load?
if 'extends' in conf:
path = get_extended_config_file(conf['extends'])
base = YamlLintConfig(file=path)
try:
self.extend(base)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
def validate(self):
for id in self.rules:
try:
rule = yamllint.rules.get(id)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
self.rules[id] = validate_rule_conf(rule, self.rules[id])
def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable':
return False
if type(conf) == dict:
if 'level' not in conf:
conf['level'] = 'error'
elif conf['level'] not in ('error', 'warning'):
raise YamlLintConfigError(
'invalid config: level should be "error" or "warning"')
options = getattr(rule, 'CONF', {})
for optkey in conf:
if optkey == 'level':
continue
if optkey not in options:
raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID))
if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]:
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))
else:
if type(conf[optkey]) != options[optkey]:
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__))
for optkey in options:
if optkey not in conf:
raise YamlLintConfigError(
'invalid config: missing option "%s" for rule "%s"' %
(optkey, rule.ID))
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % rule.ID)
return conf
def get_extended_config_file(name):
# Is it a standard conf shipped with yamllint...
if '/' not in name:
std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -33,71 +132,3 @@ def get_extended_conf(name):
# or a custom conf on filesystem?
return name
def extend_config(content):
try:
conf = yaml.safe_load(content)
if 'rules' not in conf:
conf['rules'] = {}
# Does this conf override another conf that we need to load?
if 'extends' in conf:
base = parse_config_from_file(get_extended_conf(conf['extends']))
base.update(conf['rules'])
conf['rules'] = base
return conf
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
def parse_config(content):
conf = extend_config(content)
rules = {}
for id in conf['rules']:
try:
rule = yamllint.rules.get(id)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if conf['rules'][id] == 'disable':
continue
rules[id] = {'level': 'error'}
if type(conf['rules'][id]) == dict:
if 'level' in conf['rules'][id]:
if conf['rules'][id]['level'] not in ('error', 'warning'):
raise YamlLintConfigError(
'invalid config: level should be "error" or "warning"')
rules[id]['level'] = conf['rules'][id]['level']
options = getattr(rule, 'CONF', {})
for optkey in conf['rules'][id]:
if optkey == 'level':
continue
if optkey not in options:
raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, id))
if type(conf['rules'][id][optkey]) != options[optkey]:
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s' %
(optkey, id, options[optkey].__name__))
rules[id][optkey] = conf['rules'][id][optkey]
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % id)
return rules
def parse_config_from_file(path):
with open(path) as f:
return parse_config(f.read())
def get_enabled_rules(conf):
return [yamllint.rules.get(r) for r in conf.keys() if conf[r] is not False]

View File

@@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
class LintProblem(object):
def __init__(self, line, column, desc='<no description>', rule=None):
self.line = line
self.column = column
self.desc = desc
self.rule = rule
self.level = None
@property
def message(self):
if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule)
return self.desc
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
self.rule == other.rule)
def __lt__(self, other):
return (self.line < other.line or
(self.line == other.line and self.column < other.column))
def __repr__(self):
return '%d:%d: %s' % (self.line, self.column, self.message)
class YamlLintError(Exception):
pass
class YamlLintConfigError(YamlLintError):
pass

137
yamllint/linter.py Normal file
View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import yaml
from yamllint import parser
class LintProblem(object):
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
self.line = line
#: Column on which the problem was found (starting at 1)
self.column = column
#: Human-readable description of the problem
self.desc = desc
#: Identifier of the rule that detected the problem
self.rule = rule
self.level = None
@property
def message(self):
if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule)
return self.desc
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
self.rule == other.rule)
def __lt__(self, other):
return (self.line < other.line or
(self.line == other.line and self.column < other.column))
def __repr__(self):
return '%d:%d: %s' % (self.line, self.column, self.message)
def get_costemic_problems(buffer, conf):
rules = conf.enabled_rules()
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
line_rules = [r for r in rules if r.TYPE == 'line']
context = {}
for rule in token_rules:
context[rule.ID] = {}
for elem in parser.token_or_line_generator(buffer):
if isinstance(elem, parser.Token):
for rule in token_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next,
context[rule.ID]):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
elif isinstance(elem, parser.Line):
for rule in line_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
def get_syntax_error(buffer):
try:
list(yaml.parse(buffer, Loader=yaml.BaseLoader))
except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1,
'syntax error: ' + e.problem)
problem.level = 'error'
return problem
def _run(buffer, conf):
# If the document contains a syntax error, save it and yield it at the
# right line
syntax_error = get_syntax_error(buffer)
for problem in get_costemic_problems(buffer, conf):
# Insert the syntax error (if any) at the right place...
if (syntax_error and syntax_error.line <= problem.line and
syntax_error.column <= problem.column):
yield syntax_error
# If there is already a yamllint error at the same place, discard
# it as it is probably redundant (and maybe it's just a 'warning',
# in which case the script won't even exit with a failure status).
if (syntax_error.line == problem.line and
syntax_error.column == problem.column):
syntax_error = None
continue
syntax_error = None
yield problem
if syntax_error:
yield syntax_error
def run(input, conf):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) == str:
return _run(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly
content = input.read()
return _run(content, conf)
else:
raise TypeError('input should be a string or a stream')

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def parsable_format(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
{'file': filename,
'line': problem.line,
'column': problem.column,
'level': problem.level,
'message': problem.message})
def standard_format(problem, filename):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' '
if problem.level == 'warning':
line += '\033[33m%s\033[0m' % problem.level
else:
line += '\033[31m%s\033[0m' % problem.level
line += max(38 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' \033[2m(%s)\033[0m' % problem.rule
return line

View File

@@ -16,8 +16,6 @@
import yaml
from yamllint.errors import LintProblem
class Line(object):
def __init__(self, line_no, buffer, start, end):
@@ -85,11 +83,3 @@ def token_or_line_generator(buffer):
else:
yield token
token = next(token_gen, None)
def get_syntax_error(buffer):
try:
yaml.safe_load_all(buffer)
except yaml.error.MarkedYAMLError as e:
return LintProblem(e.problem_mark.line + 1, e.problem_mark.column + 1,
'syntax error: ' + e.problem)

View File

@@ -15,7 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.rules import (
braces,
brackets,
colons,
commas,
comments,
comments_indentation,
document_end,
document_start,
empty_lines,
@@ -28,7 +33,12 @@ from yamllint.rules import (
)
_RULES = {
braces.ID: braces,
brackets.ID: brackets,
colons.ID: colons,
commas.ID: commas,
comments.ID: comments,
comments_indentation.ID: comments_indentation,
document_end.ID: document_end,
document_start.ID: document_start,
empty_lines.ID: empty_lines,

95
yamllint/rules/braces.py Normal file
View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces inside braces (``{`` and ``}``).
.. rubric:: Options
* ``min-spaces-inside`` defines the minimal number of spaces required inside
braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
braces.
.. rubric:: Examples
#. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
::
object: {key1: 4, key2: 8}
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside: 1, max-spaces-inside: 3}``
the following code snippet would **PASS**:
::
object: { key1: 4, key2: 8 }
the following code snippet would **PASS**:
::
object: { key1: 4, key2: 8 }
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
the following code snippet would **FAIL**:
::
object: {key1: 4, key2: 8 }
"""
import yaml
from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
def check(conf, token, prev, next, context):
if isinstance(token, yaml.FlowMappingStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],
min_desc='too few spaces inside braces',
max_desc='too many spaces inside braces')
if problem is not None:
yield problem
elif (isinstance(token, yaml.FlowMappingEndToken) and
(prev is None or
not isinstance(prev, yaml.FlowMappingStartToken))):
problem = spaces_before(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],
min_desc='too few spaces inside braces',
max_desc='too many spaces inside braces')
if problem is not None:
yield problem

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces inside brackets (``[`` and
``]``).
.. rubric:: Options
* ``min-spaces-inside`` defines the minimal number of spaces required inside
brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
brackets.
.. rubric:: Examples
#. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
::
object: [1, 2, abc]
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
#. With ``brackets: {min-spaces-inside: 1, max-spaces-inside: 3}``
the following code snippet would **PASS**:
::
object: [ 1, 2, abc ]
the following code snippet would **PASS**:
::
object: [ 1, 2, abc ]
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
the following code snippet would **FAIL**:
::
object: [1, 2, abc ]
"""
import yaml
from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
def check(conf, token, prev, next, context):
if isinstance(token, yaml.FlowSequenceStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],
min_desc='too few spaces inside brackets',
max_desc='too many spaces inside brackets')
if problem is not None:
yield problem
elif (isinstance(token, yaml.FlowSequenceEndToken) and
(prev is None or
not isinstance(prev, yaml.FlowSequenceStartToken))):
problem = spaces_before(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],
min_desc='too few spaces inside brackets',
max_desc='too many spaces inside brackets')
if problem is not None:
yield problem

View File

@@ -14,9 +14,65 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces before and after colons (``:``).
.. rubric:: Options
* ``max-spaces-before`` defines the maximal number of spaces allowed before
colons (use ``-1`` to disable).
* ``max-spaces-after`` defines the maximal number of spaces allowed after
colons (use ``-1`` to disable).
.. rubric:: Examples
#. With ``colons: {max-spaces-before: 0, max-spaces-after: 1}``
the following code snippet would **PASS**:
::
object:
- a
- b
key: value
#. With ``colons: {max-spaces-before: 1}``
the following code snippet would **PASS**:
::
object :
- a
- b
the following code snippet would **FAIL**:
::
object :
- a
- b
#. With ``colons: {max-spaces-after: 2}``
the following code snippet would **PASS**:
::
first: 1
second: 2
third: 3
the following code snippet would **FAIL**:
::
first: 1
2nd: 2
third: 3
"""
import yaml
from yamllint.errors import LintProblem
from yamllint.rules.common import spaces_after, spaces_before, is_explicit_key
ID = 'colons'
@@ -25,22 +81,23 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': int}
def check(conf, token, prev, next):
def check(conf, token, prev, next, context):
if isinstance(token, yaml.ValueToken):
if (prev is not None and
prev.end_mark.line == token.start_mark.line and
conf['max-spaces-before'] != - 1 and
(prev.end_mark.pointer + conf['max-spaces-before'] <
token.start_mark.pointer)):
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column,
'too many spaces before colon')
problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'],
max_desc='too many spaces before colon')
if problem is not None:
yield problem
if (next is not None and
token.end_mark.line == next.start_mark.line and
conf['max-spaces-after'] != - 1 and
(next.start_mark.pointer - token.end_mark.pointer >
conf['max-spaces-after'])):
yield LintProblem(token.start_mark.line + 1,
next.start_mark.column,
'too many spaces after colon')
problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'],
max_desc='too many spaces after colon')
if problem is not None:
yield problem
if isinstance(token, yaml.KeyToken) and is_explicit_key(token):
problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'],
max_desc='too many spaces after question mark')
if problem is not None:
yield problem

94
yamllint/rules/commas.py Normal file
View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces before and after commas (``,``).
.. rubric:: Options
* ``max-spaces-before`` defines the maximal number of spaces allowed before
commas (use ``-1`` to disable).
* ``max-spaces-after`` defines the maximal number of spaces allowed after
commas (use ``-1`` to disable).
.. rubric:: Examples
#. With ``commas: {max-spaces-before: 0, max-spaces-after: 1}``
the following code snippet would **PASS**:
::
strange var:
[10, 20, 30, {x: 1, y: 2}]
the following code snippet would **FAIL**:
::
strange var:
[10, 20 , 30, {x: 1, y: 2}]
the following code snippet would **FAIL**:
::
strange var:
[10, 20, 30, {x: 1, y: 2}]
#. With ``commas: {max-spaces-before: 2, max-spaces-after: 2}``
the following code snippet would **PASS**:
::
strange var:
[10 , 20 , 30, {x: 1 , y: 2}]
the following code snippet would **FAIL**:
::
strange var:
[10 , 20 , 30, {x: 1 , y: 2}]
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
ID = 'commas'
TYPE = 'token'
CONF = {'max-spaces-before': int,
'max-spaces-after': int}
def check(conf, token, prev, next, context):
if isinstance(token, yaml.FlowEntryToken):
if prev is not None and prev.end_mark.line < token.start_mark.line:
yield LintProblem(token.start_mark.line + 1,
max(1, token.start_mark.column),
'too many spaces before comma')
else:
problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'],
max_desc='too many spaces before comma')
if problem is not None:
yield problem
problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'],
max_desc='too many spaces after comma')
if problem is not None:
yield problem

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the position and formatting of comments.
.. rubric:: Options
* Use ``require-starting-space`` to require a space character right after the
``#``. Set to ``yes`` to enable, ``no`` to disable.
* ``min-spaces-from-content`` is used to visually separate inline comments from
content. It defines the minimal required number of spaces between a comment
and its preceding content.
.. rubric:: Examples
#. With ``comments: {require-starting-space: yes}``
the following code snippet would **PASS**:
::
# This sentence
# is a block comment
the following code snippet would **FAIL**:
::
#This sentence
#is a block comment
#. With ``comments: {min-spaces-from-content: 2}``
the following code snippet would **PASS**:
::
x = 2 ^ 127 - 1 # Mersenne prime number
the following code snippet would **FAIL**:
::
x = 2 ^ 127 - 1 # Mersenne prime number
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_comments_between_tokens
ID = 'comments'
TYPE = 'token'
CONF = {'require-starting-space': bool,
'min-spaces-from-content': int}
def check(conf, token, prev, next, context):
for comment in get_comments_between_tokens(token, next):
if (conf['min-spaces-from-content'] != -1 and
not isinstance(token, yaml.StreamStartToken) and
comment.line == token.end_mark.line + 1):
# Sometimes token end marks are on the next line
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
if (comment.pointer - token.end_mark.pointer <
conf['min-spaces-from-content']):
yield LintProblem(comment.line, comment.column,
'too few spaces before comment')
if (conf['require-starting-space'] and
comment.pointer + 1 < len(comment.buffer) and
comment.buffer[comment.pointer + 1] != ' ' and
comment.buffer[comment.pointer + 1] != '\n'):
yield LintProblem(comment.line, comment.column + 1,
'missing starting space in comment')

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to force comments to be indented like content.
.. rubric:: Examples
#. With ``comments-indentation: {}``
the following code snippet would **PASS**:
::
# Fibonacci
[0, 1, 1, 2, 3, 5]
the following code snippet would **FAIL**:
::
# Fibonacci
[0, 1, 1, 2, 3, 5]
the following code snippet would **PASS**:
::
list:
- 2
- 3
# - 4
- 5
the following code snippet would **FAIL**:
::
list:
- 2
- 3
# - 4
- 5
the following code snippet would **PASS**:
::
# This is the first object
obj1:
- item A
# - item B
# This is the second object
obj2: []
the following code snippet would **PASS**:
::
# This sentence
# is a block comment
the following code snippet would **FAIL**:
::
# This sentence
# is a block comment
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_line_indent, get_comments_between_tokens
ID = 'comments-indentation'
TYPE = 'token'
# Case A:
#
# prev: line:
# # commented line
# current: line
#
# Case B:
#
# prev: line
# # commented line 1
# # commented line 2
# current: line
def check(conf, token, prev, next, context):
if prev is None:
return
curr_line_indent = token.start_mark.column
if isinstance(token, yaml.StreamEndToken):
curr_line_indent = 0
skip_first_line = True
if isinstance(prev, yaml.StreamStartToken):
skip_first_line = False
prev_line_indent = 0
else:
prev_line_indent = get_line_indent(prev)
if prev_line_indent <= curr_line_indent:
prev_line_indent = -1 # disable it
for comment in get_comments_between_tokens(
prev, token, skip_first_line=skip_first_line):
if comment.column - 1 == curr_line_indent:
prev_line_indent = -1 # disable it
elif comment.column - 1 != prev_line_indent:
yield LintProblem(comment.line, comment.column,
'comment not indented like content')

118
yamllint/rules/common.py Normal file
View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import yaml
from yamllint.linter import LintProblem
def spaces_after(token, prev, next, min=-1, max=-1,
min_desc=None, max_desc=None):
if next is not None and token.end_mark.line == next.start_mark.line:
spaces = next.start_mark.pointer - token.end_mark.pointer
if max != - 1 and spaces > max:
return LintProblem(token.start_mark.line + 1,
next.start_mark.column, max_desc)
elif min != - 1 and spaces < min:
return LintProblem(token.start_mark.line + 1,
next.start_mark.column + 1, min_desc)
def spaces_before(token, prev, next, min=-1, max=-1,
min_desc=None, max_desc=None):
if (prev is not None and prev.end_mark.line == token.start_mark.line and
# Discard tokens (only scalars?) that end at the start of next line
(prev.end_mark.pointer == 0 or
prev.end_mark.buffer[prev.end_mark.pointer - 1] != '\n')):
spaces = token.start_mark.pointer - prev.end_mark.pointer
if max != - 1 and spaces > max:
return LintProblem(token.start_mark.line + 1,
token.start_mark.column, max_desc)
elif min != - 1 and spaces < min:
return LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1, min_desc)
class Comment(object):
def __init__(self, line, column, buffer, pointer):
self.line = line
self.column = column
self.buffer = buffer
self.pointer = pointer
def __repr__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)
if end != -1:
return self.buffer[self.pointer:end]
return self.buffer[self.pointer:]
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
str(self) == str(other))
def get_line_indent(token):
"""Finds the indent of the line the token starts in."""
start = token.start_mark.buffer.rfind('\n', 0,
token.start_mark.pointer) + 1
content = start
while token.start_mark.buffer[content] == ' ':
content += 1
return content - start
def get_comments_between_tokens(token1, token2, skip_first_line=False):
if token2 is None:
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
elif (token1.end_mark.line == token2.start_mark.line and
not isinstance(token1, yaml.StreamStartToken) and
not isinstance(token2, yaml.StreamEndToken)):
return
else:
buf = token1.end_mark.buffer[token1.end_mark.pointer:
token2.start_mark.pointer]
line_no = token1.end_mark.line + 1
column_no = token1.end_mark.column + 1
pointer = token1.end_mark.pointer
for line in buf.split('\n'):
if skip_first_line:
skip_first_line = False
else:
pos = line.find('#')
if pos != -1:
yield Comment(line_no, column_no + pos,
token1.end_mark.buffer, pointer + pos)
pointer += len(line) + 1
line_no += 1
column_no = 1
def is_explicit_key(token):
# explicit key:
# ? key
# : v
# or
# ?
# key
# : v
return (token.start_mark.pointer < token.end_mark.pointer and
token.start_mark.buffer[token.start_mark.pointer] == '?')

View File

@@ -14,9 +14,69 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to require or forbid the use of document end marker (``...``).
.. rubric:: Options
* Set ``present`` to ``yes`` when the document end marker is required, or to
``no`` when it is forbidden.
.. rubric:: Examples
#. With ``document-end: {present: yes}``
the following code snippet would **PASS**:
::
---
this:
is: [a, document]
...
---
- this
- is: another one
...
the following code snippet would **FAIL**:
::
---
this:
is: [a, document]
---
- this
- is: another one
...
#. With ``document-end: {present: no}``
the following code snippet would **PASS**:
::
---
this:
is: [a, document]
---
- this
- is: another one
the following code snippet would **FAIL**:
::
---
this:
is: [a, document]
...
---
- this
- is: another one
"""
import yaml
from yamllint.errors import LintProblem
from yamllint.linter import LintProblem
ID = 'document-end'
@@ -24,7 +84,7 @@ TYPE = 'token'
CONF = {'present': bool}
def check(conf, token, prev, next):
def check(conf, token, prev, next, context):
if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and
not (isinstance(prev, yaml.DocumentEndToken) or

View File

@@ -14,9 +14,59 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to require or forbid the use of document start marker (``---``).
.. rubric:: Options
* Set ``present`` to ``yes`` when the document start marker is required, or to
``no`` when it is forbidden.
.. rubric:: Examples
#. With ``document-start: {present: yes}``
the following code snippet would **PASS**:
::
---
this:
is: [a, document]
---
- this
- is: another one
the following code snippet would **FAIL**:
::
this:
is: [a, document]
---
- this
- is: another one
#. With ``document-start: {present: no}``
the following code snippet would **PASS**:
::
this:
is: [a, document]
...
the following code snippet would **FAIL**:
::
---
this:
is: [a, document]
...
"""
import yaml
from yamllint.errors import LintProblem
from yamllint.linter import LintProblem
ID = 'document-start'
@@ -24,15 +74,14 @@ TYPE = 'token'
CONF = {'present': bool}
# TODO: Don't fail if document contains directives such as
# %YAML 1.2
def check(conf, token, prev, next):
def check(conf, token, prev, next, context):
if conf['present']:
if ((isinstance(prev, yaml.StreamStartToken) or
isinstance(prev, yaml.DocumentEndToken)) and
not (isinstance(token, yaml.DocumentStartToken) or
isinstance(token, yaml.StreamEndToken))):
if (isinstance(prev, (yaml.StreamStartToken,
yaml.DocumentEndToken,
yaml.DirectiveToken)) and
not isinstance(token, (yaml.DocumentStartToken,
yaml.DirectiveToken,
yaml.StreamEndToken))):
yield LintProblem(token.start_mark.line + 1, 1,
'missing document start "---"')

View File

@@ -14,7 +14,43 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.errors import LintProblem
"""
Use this rule to set a maximal number of allowed consecutive blank lines.
.. rubric:: Options
* ``max`` defines the maximal number of empty lines allowed in the document.
* ``max-start`` defines the maximal number of empty lines allowed at the
beginning of the file. This option takes precedence over ``max``.
* ``max-end`` defines the maximal number of empty lines allowed at the end of
the file. This option takes precedence over ``max``.
.. rubric:: Examples
#. With ``empty-lines: {max: 1}``
the following code snippet would **PASS**:
::
- foo:
- 1
- 2
- bar: [3, 4]
the following code snippet would **FAIL**:
::
- foo:
- 1
- 2
- bar: [3, 4]
"""
from yamllint.linter import LintProblem
ID = 'empty-lines'

View File

@@ -14,9 +14,63 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces after hyphens (``-``).
.. rubric:: Options
* ``max-spaces-after`` defines the maximal number of spaces allowed after
hyphens.
.. rubric:: Examples
#. With ``hyphens: {max-spaces-after: 1}``
the following code snippet would **PASS**:
::
- first list:
- a
- b
- - 1
- 2
- 3
the following code snippet would **FAIL**:
::
- first list:
- a
- b
the following code snippet would **FAIL**:
::
- - 1
- 2
- 3
#. With ``hyphens: {max-spaces-after: 3}``
the following code snippet would **PASS**:
::
- key
- key2
- key42
the following code snippet would **FAIL**:
::
- key
- key2
- key42
"""
import yaml
from yamllint.errors import LintProblem
from yamllint.rules.common import spaces_after
ID = 'hyphens'
@@ -24,11 +78,10 @@ TYPE = 'token'
CONF = {'max-spaces-after': int}
def check(conf, token, prev, next):
def check(conf, token, prev, next, context):
if isinstance(token, yaml.BlockEntryToken):
if token.end_mark.line == next.start_mark.line:
if (next.start_mark.pointer - token.end_mark.pointer >
conf['max-spaces-after']):
yield LintProblem(token.start_mark.line + 1,
next.start_mark.column,
'too many spaces after hyphen')
problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'],
max_desc='too many spaces after hyphen')
if problem is not None:
yield problem

View File

@@ -14,50 +14,380 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the indentation.
.. rubric:: Options
* ``spaces`` defines the number of spaces that represent an indentation level.
* ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``,
``no`` and ``whatever`` (the latter means either indenting or not indenting
block sequences is OK.
* ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable.
.. rubric:: Examples
#. With ``indentation: {spaces: 1}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
#. With ``indentation: {spaces: 4}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
the following code snippet would **FAIL**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
#. With ``indentation: {spaces: 2, indent-sequences: no}``
the following code snippet would **PASS**:
::
list:
- flying
- spaghetti
- monster
the following code snippet would **FAIL**:
::
list:
- flying
- spaghetti
- monster
#. With ``indentation: {spaces: 2, indent-sequences: whatever}``
the following code snippet would **PASS**:
::
list:
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}``
the following code snippet would **PASS**:
::
Blaise Pascal:
Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte.
the following code snippet would **PASS**:
::
Blaise Pascal: Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte.
the following code snippet would **FAIL**:
::
Blaise Pascal: Je vous écris une longue lettre parce que
je n'ai pas le temps d'en écrire une courte.
the following code snippet would **FAIL**:
::
C code:
void main() {
printf("foo");
}
the following code snippet would **PASS**:
::
C code:
void main() {
printf("bar");
}
"""
import yaml
from yamllint.errors import LintProblem
from yamllint.linter import LintProblem
from yamllint.rules.common import is_explicit_key
ID = 'indentation'
TYPE = 'token'
CONF = {'spaces': int}
CONF = {'spaces': int,
'indent-sequences': (True, False, 'whatever'),
'check-multi-line-strings': bool}
ROOT, MAP, B_SEQ, F_SEQ, KEY, VAL = range(6)
def check(conf, token, prev, next):
if isinstance(token, yaml.StreamEndToken):
class Parent(object):
def __init__(self, type, indent):
self.type = type
self.indent = indent
self.explicit_key = False
def check_scalar_indentation(conf, token, context):
if token.start_mark.line == token.end_mark.line:
return
if (prev is None or isinstance(prev, yaml.StreamStartToken) or
isinstance(prev, yaml.DirectiveToken) or
isinstance(prev, yaml.DocumentStartToken)):
if token.start_mark.column != 0:
yield LintProblem(
token.end_mark.line + 1, token.start_mark.column + 1,
'found indentation of %d instead of %d' %
(token.start_mark.column, 0))
return
if token.plain:
expected_indent = token.start_mark.column
elif token.style in ('"', "'"):
expected_indent = token.start_mark.column + 1
elif token.style in ('>', '|'):
if context['stack'][-1].type == B_SEQ:
# - >
# multi
# line
expected_indent = token.start_mark.column + conf['spaces']
elif context['stack'][-1].type == KEY:
assert context['stack'][-1].explicit_key
# - ? >
# multi-line
# key
# : >
# multi-line
# value
expected_indent = token.start_mark.column + conf['spaces']
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
expected_indent = context['stack'][-1].indent + conf['spaces']
elif context['stack'][-2].explicit_key:
# - ? key
# : >
# multi-line
# value
expected_indent = token.start_mark.column + conf['spaces']
else:
# - key: >
# multi
# line
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
if token.start_mark.line > prev.end_mark.line:
buffer = prev.end_mark.buffer
line_no = token.start_mark.line + 1
start = buffer.rfind('\n', 0, prev.end_mark.pointer) + 1
prev_indent = 0
line_start = token.start_mark.pointer
while True:
line_start = token.start_mark.buffer.find(
'\n', line_start, token.end_mark.pointer - 1) + 1
if line_start == 0:
break
line_no += 1
# YAML recognizes two white space characters: space and tab.
# http://yaml.org/spec/1.2/spec.html#id2775170
while buffer[start + prev_indent] in ' \t':
prev_indent += 1
indent = 0
while token.start_mark.buffer[line_start + indent] == ' ':
indent += 1
if token.start_mark.buffer[line_start + indent] == '\n':
continue
# Discard any leading '- '
if (buffer[start + prev_indent:start + prev_indent + 2] == '- '):
prev_indent += 2
while buffer[start + prev_indent] in ' \t':
prev_indent += 1
if indent != expected_indent:
yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' %
(expected_indent, indent))
if (token.start_mark.column > prev_indent and
token.start_mark.column != prev_indent + conf['spaces']):
yield LintProblem(
token.end_mark.line + 1, token.start_mark.column + 1,
'found indentation of %d instead of %d' %
(token.start_mark.column, prev_indent + conf['spaces']))
def check(conf, token, prev, next, context):
if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1
# Step 1: Lint
needs_lint = (
not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
not isinstance(token, yaml.BlockEndToken) and
not (isinstance(token, yaml.ScalarToken) and token.value == '') and
token.start_mark.line + 1 > context['cur_line'])
if needs_lint:
found_indentation = token.start_mark.column
expected = context['stack'][-1].indent
if isinstance(token, (yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
expected = 0
elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)):
expected += conf['spaces']
if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
'wrong indentation: expected %d but found %d' %
(expected, found_indentation))
if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']):
for problem in check_scalar_indentation(conf, token, context):
yield problem
# Step 2.a:
if needs_lint:
context['cur_line_indent'] = found_indentation
context['cur_line'] = token.end_mark.line + 1
# Step 2.b: Update state
if isinstance(token, yaml.BlockMappingStartToken):
assert isinstance(next, yaml.KeyToken)
if next.start_mark.line == token.start_mark.line:
# - a: 1
# b: 2
# or
# - ? a
# : 1
indent = token.start_mark.column
else:
# - ?
# a
# : 1
indent = token.start_mark.column + conf['spaces']
context['stack'].append(Parent(MAP, indent))
elif isinstance(token, yaml.FlowMappingStartToken):
if next.start_mark.line == token.start_mark.line:
# - {a: 1, b: 2}
indent = next.start_mark.column
else:
# - {
# a: 1, b: 2
# }
indent = context['cur_line_indent'] + conf['spaces']
context['stack'].append(Parent(MAP, indent))
elif isinstance(token, yaml.BlockSequenceStartToken):
# - - a
# - b
assert next.start_mark.line == token.start_mark.line
assert isinstance(next, yaml.BlockEntryToken)
indent = token.start_mark.column
context['stack'].append(Parent(B_SEQ, indent))
elif isinstance(token, yaml.FlowSequenceStartToken):
if next.start_mark.line == token.start_mark.line:
# - [a, b]
indent = next.start_mark.column
else:
# - [
# a, b
# ]
indent = context['cur_line_indent'] + conf['spaces']
context['stack'].append(Parent(F_SEQ, indent))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
assert context['stack'][-1].type in (MAP, B_SEQ, F_SEQ)
context['stack'].pop()
elif isinstance(token, yaml.KeyToken):
indent = context['stack'][-1].indent
context['stack'].append(Parent(KEY, indent))
context['stack'][-1].explicit_key = is_explicit_key(token)
if context['stack'][-1].type == VAL:
context['stack'].pop()
assert context['stack'][-1].type == KEY
context['stack'].pop()
elif isinstance(token, yaml.ValueToken):
assert context['stack'][-1].type == KEY
# Discard empty values
if isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken)):
context['stack'].pop()
else:
if context['stack'][-1].explicit_key:
# ? k
# : value
# or
# ? k
# :
# value
indent = context['stack'][-1].indent + conf['spaces']
elif next.start_mark.line == prev.start_mark.line:
# k: value
indent = next.start_mark.column
elif isinstance(next, (yaml.BlockSequenceStartToken,
yaml.BlockEntryToken)):
# NOTE: We add BlockEntryToken in the test above because
# sometimes BlockSequenceStartToken are not issued. Try
# yaml.scan()ning this:
# '- lib:\n'
# ' - var\n'
if conf['indent-sequences'] is False:
indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces']
else: # 'whatever'
if next.start_mark.column == context['stack'][-1].indent:
# key:
# - e1
# - e2
indent = context['stack'][-1].indent
else:
# key:
# - e1
# - e2
indent = context['stack'][-1].indent + conf['spaces']
else:
# k:
# value
indent = context['stack'][-1].indent + conf['spaces']
context['stack'].append(Parent(VAL, indent))

View File

@@ -14,16 +14,88 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.errors import LintProblem
"""
Use this rule to set a limit to lines length.
.. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines.
* ``allow-non-breakable-words`` is used to allow non breakable words (without
spaces inside) to overflow the limit. This is useful for long URLs, for
instance. Use ``yes`` to allow, ``no`` to forbid.
.. rubric:: Examples
#. With ``line-length: {max: 70}``
the following code snippet would **PASS**:
::
long sentence:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua.
the following code snippet would **FAIL**:
::
long sentence:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
#. With ``line-length: {max: 60, allow-non-breakable-words: yes}``
the following code snippet would **PASS**:
::
this:
is:
- a:
http://localhost/very/very/very/very/very/very/very/very/long/url
# this comment is too long,
# but hard to split:
# http://localhost/another/very/very/very/very/very/very/very/very/long/url
the following code snippet would **FAIL**:
::
- this line is waaaaaaaaaaaaaay too long but could be easily splitted...
#. With ``line-length: {max: 60, allow-non-breakable-words: no}``
the following code snippet would **FAIL**:
::
this:
is:
- a:
http://localhost/very/very/very/very/very/very/very/very/long/url
"""
from yamllint.linter import LintProblem
ID = 'line-length'
TYPE = 'line'
CONF = {'max': int}
CONF = {'max': int,
'allow-non-breakable-words': bool}
def check(conf, line):
if line.end - line.start > conf['max']:
if conf['allow-non-breakable-words']:
start = line.start
while start < line.end and line.buffer[start] == ' ':
start += 1
if start != line.end:
if line.buffer[start] == '#':
start += 2
if line.buffer.find(' ', start, line.end) == -1:
return
yield LintProblem(line.line_no, conf['max'] + 1,
'line too long (%d > %d characters)' %
(line.end - line.start, conf['max']))

View File

@@ -14,7 +14,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.errors import LintProblem
"""
Use this rule to require a new line character (``\\n``) at the end of files.
The POSIX standard `requires the last line to end with a new line character
<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206>`_.
All UNIX tools expect a new line at the end of files. Most text editors use
this convention too.
"""
from yamllint.linter import LintProblem
ID = 'new-line-at-end-of-file'

View File

@@ -14,12 +14,22 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.errors import LintProblem
"""
Use this rule to force the type of new line characters.
.. rubric:: Options
* Set ``type`` to ``unix`` to use UNIX-typed new line characters (``\\n``), or
``dos`` to use DOS-typed new line characters (``\\r\\n``).
"""
from yamllint.linter import LintProblem
ID = 'new-lines'
TYPE = 'line'
CONF = {'type': str}
CONF = {'type': ('unix', 'dos')}
def check(conf, line):

View File

@@ -14,9 +14,32 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid trailing spaces at the end of lines.
.. rubric:: Examples
#. With ``trailing-spaces: {}``
the following code snippet would **PASS**:
::
this document doesn't contain
any trailing
spaces
the following code snippet would **FAIL**:
::
this document contains """ """
trailing spaces
on lines 1 and 3 """ """
"""
import string
from yamllint.errors import LintProblem
from yamllint.linter import LintProblem
ID = 'trailing-spaces'