diff --git a/.travis.yml b/.travis.yml index c40c6bd..f27f4bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ install: - pip install . script: - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi + # Because of https://github.com/PyCQA/flake8-import-order/issues/149 + # otherwise tests fail with Python 3.6: + - pip uninstall --yes enum34 - yamllint --strict $(git ls-files '*.yaml' '*.yml') - coverage run --source=yamllint setup.py test - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc1fec4..1127c82 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,11 @@ Changelog ========= +1.11.0 (2018-02-21) +------------------- + +- Add a new `octal-values` rule + 1.10.0 (2017-11-05) ------------------- diff --git a/docs/rules.rst b/docs/rules.rst index 24318c7..3900c28 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -99,6 +99,11 @@ new-lines .. automodule:: yamllint.rules.new_lines +octal-values +------------ + +.. automodule:: yamllint.rules.octal_values + trailing-spaces --------------- diff --git a/setup.cfg b/setup.cfg index 82ba8ec..bd753fb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,7 @@ universal = 1 [flake8] import-order-style = pep8 +application-import-names = yamllint [build_sphinx] all-files = 1 diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py index 374f23c..3632a25 100644 --- a/tests/rules/test_indentation.py +++ b/tests/rules/test_indentation.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from tests.common import RuleTestCase + from yamllint.parser import token_or_comment_generator, Comment from yamllint.rules.indentation import check diff --git a/tests/rules/test_octal_values.py b/tests/rules/test_octal_values.py new file mode 100644 index 0000000..7e82d4e --- /dev/null +++ b/tests/rules/test_octal_values.py @@ -0,0 +1,72 @@ +# -*- 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 . + +from tests.common import RuleTestCase + + +class OctalValuesTestCase(RuleTestCase): + rule_id = 'octal-values' + + def test_disabled(self): + conf = ('octal-values: disable\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('user-city: 010', conf) + self.check('user-city: 0o10', conf) + + def test_implicit_octal_values(self): + conf = ('octal-values: {forbid-implicit-octal: true}\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('user-city: 010', conf, problem=(1, 15)) + self.check('user-city: abc', conf) + self.check('user-city: 010,0571', conf) + self.check("user-city: '010'", conf) + self.check('user-city: "010"', conf) + self.check('user-city:\n' + ' - 010', conf, problem=(2, 8)) + self.check('user-city: [010]', conf, problem=(1, 16)) + self.check('user-city: {beijing: 010}', conf, problem=(1, 25)) + self.check('explicit-octal: 0o10', conf) + self.check('not-number: 0abc', conf) + self.check('zero: 0', conf) + self.check('hex-value: 0x10', conf) + self.check('number-values:\n' + ' - 0.10\n' + ' - .01\n' + ' - 0e3\n', conf) + + def test_explicit_octal_values(self): + conf = ('octal-values: {forbid-explicit-octal: true}\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('user-city: 0o10', conf, problem=(1, 16)) + self.check('user-city: abc', conf) + self.check('user-city: 0o10,0571', conf) + self.check("user-city: '0o10'", conf) + self.check('user-city:\n' + ' - 0o10', conf, problem=(2, 9)) + self.check('user-city: [0o10]', conf, problem=(1, 17)) + self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26)) + self.check('implicit-octal: 010', conf) + self.check('not-number: 0oabc', conf) + self.check('zero: 0', conf) + self.check('hex-value: 0x10', conf) + self.check('number-values:\n' + ' - 0.10\n' + ' - .01\n' + ' - 0e3\n', conf) + self.check('user-city: "010"', conf) diff --git a/tests/test_cli.py b/tests/test_cli.py index 47b5daf..341e3fc 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -30,10 +30,10 @@ try: except AssertionError: import unittest2 as unittest -from yamllint import cli - from tests.common import build_temp_workspace +from yamllint import cli + @unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported') class CommandLineTestCase(unittest.TestCase): diff --git a/tests/test_config.py b/tests/test_config.py index 7e719b1..3b6b9c2 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -27,11 +27,11 @@ try: except AssertionError: import unittest2 as unittest +from tests.common import build_temp_workspace + from yamllint import cli from yamllint import config -from tests.common import build_temp_workspace - class SimpleConfigTestCase(unittest.TestCase): def test_parse_config(self): diff --git a/yamllint/__init__.py b/yamllint/__init__.py index 8ff52f9..f86c4ea 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -22,7 +22,7 @@ indentation, etc.""" APP_NAME = 'yamllint' -APP_VERSION = '1.10.0' +APP_VERSION = '1.11.0' APP_DESCRIPTION = __doc__ __author__ = u'Adrien Vergé' diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index 8c0e89a..8432c9a 100644 --- a/yamllint/conf/default.yaml +++ b/yamllint/conf/default.yaml @@ -50,6 +50,9 @@ rules: new-line-at-end-of-file: enable new-lines: type: unix + octal-values: + forbid-implicit-octal: false + forbid-explicit-octal: false trailing-spaces: enable truthy: level: warning diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py index 189ec69..d08a326 100644 --- a/yamllint/rules/__init__.py +++ b/yamllint/rules/__init__.py @@ -32,6 +32,7 @@ from yamllint.rules import ( line_length, new_line_at_end_of_file, new_lines, + octal_values, trailing_spaces, truthy, ) @@ -54,6 +55,7 @@ _RULES = { line_length.ID: line_length, new_line_at_end_of_file.ID: new_line_at_end_of_file, new_lines.ID: new_lines, + octal_values.ID: octal_values, trailing_spaces.ID: trailing_spaces, truthy.ID: truthy, } diff --git a/yamllint/rules/octal_values.py b/yamllint/rules/octal_values.py new file mode 100644 index 0000000..33c0793 --- /dev/null +++ b/yamllint/rules/octal_values.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 ScienJus +# +# 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 . + +""" +Use this rule to prevent values with octal numbers. In YAML, numbers that +start with ``0`` are interpreted as octal, but this is not always wanted. +For instance ``010`` is the city code of Beijing, and should not be +converted to ``8``. + +.. rubric:: Examples + +#. With ``octal-values: {forbid-implicit-octal: true}`` + + the following code snippets would **PASS**: + :: + + user: + city-code: '010' + + the following code snippets would **PASS**: + :: + + user: + city-code: 010,021 + + the following code snippets would **FAIL**: + :: + + user: + city-code: 010 + +#. With ``octal-values: {forbid-explicit-octal: true}`` + + the following code snippets would **PASS**: + :: + + user: + city-code: '0o10' + + the following code snippets would **FAIL**: + :: + + user: + city-code: 0o10 +""" + +import yaml + +from yamllint.linter import LintProblem + + +ID = 'octal-values' +TYPE = 'token' +CONF = {'forbid-implicit-octal': bool, + 'forbid-explicit-octal': bool} + + +def check(conf, token, prev, next, nextnext, context): + if prev and isinstance(prev, yaml.tokens.TagToken): + return + + if conf['forbid-implicit-octal']: + if isinstance(token, yaml.tokens.ScalarToken): + if not token.style: + val = token.value + if val.isdigit() and len(val) > 1 and val[0] == '0': + yield LintProblem( + token.start_mark.line + 1, token.end_mark.column + 1, + 'forbidden implicit octal value "%s"' % + token.value) + + if conf['forbid-explicit-octal']: + if isinstance(token, yaml.tokens.ScalarToken): + if not token.style: + val = token.value + if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit(): + yield LintProblem( + token.start_mark.line + 1, token.end_mark.column + 1, + 'forbidden explicit octal value "%s"' % + token.value)