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/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/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)