Add list-ordering rule

This is based on the key-ordering rule and behaves the same with regards to locales.

Closes #454
pull/541/head
Kevin Wojniak 2 years ago
parent 06db2af9b0
commit f014ea9942

@ -243,9 +243,9 @@ It is possible to set the ``locale`` option globally. This is passed to Python's
so an empty string ``""`` will use the system default locale, while e.g. so an empty string ``""`` will use the system default locale, while e.g.
``"en_US.UTF-8"`` will use that. ``"en_US.UTF-8"`` will use that.
Currently this only affects the ``key-ordering`` rule. The default will order Currently this only affects the ``key-ordering`` and ``list-ordering`` rules.
by Unicode code point number, while locales will sort case and accents The default will order by Unicode code point number, while locales will sort
properly as well. case and accents properly as well.
.. code-block:: yaml .. code-block:: yaml

@ -95,6 +95,11 @@ line-length
.. automodule:: yamllint.rules.line_length .. automodule:: yamllint.rules.line_length
list-ordering
--------------
.. automodule:: yamllint.rules.list_ordering
new-line-at-end-of-file new-line-at-end-of-file
----------------------- -----------------------

@ -0,0 +1,133 @@
# Copyright (C) 2023 Johannes F. Knauf and Kevin Wojniak
#
# 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 locale
from tests.common import RuleTestCase
class ListOrderingTestCase(RuleTestCase):
rule_id = 'list-ordering'
def test_disabled(self):
conf = 'list-ordering: disable'
self.check('---\n'
'- seconditem\n'
'- firstitem\n', conf)
self.check('---\n'
'[seconditem, firstitem]\n', conf)
self.check('---\n'
'- second\n'
'- at\n', conf)
def test_enabled(self):
conf = 'list-ordering: enable'
self.check('---\n'
'- seconditem\n'
'- firstitem\n', conf,
problem=(3, 3))
self.check('---\n'
'[seconditem, firstitem]\n', conf,
problem=(2, 14))
self.check('---\n'
'- second\n'
'- at\n', conf,
problem=(3, 3))
self.check('---\n'
'nested but OK:\n'
' - third: [second]\n'
' - first\n', conf)
self.check('---\n'
'nested failure:\n'
' - third\n'
' - first:\n'
' items:\n'
' - z\n'
' - a\n', conf,
problem=(7, 9))
def test_word_length(self):
conf = 'list-ordering: enable'
self.check('---\n'
'- a\n'
'- ab\n'
'- abc\n', conf)
self.check('---\n'
'- a\n'
'- abc\n'
'- ab\n', conf,
problem=(4, 3))
def test_case(self):
conf = 'list-ordering: enable'
self.check('---\n'
'- T-shirt\n'
'- T-shirts\n'
'- t-shirt\n'
'- t-shirts\n', conf)
self.check('---\n'
'- T-shirt\n'
'- t-shirt\n'
'- T-shirts\n'
'- t-shirts\n', conf,
problem=(4, 3))
def test_accents(self):
conf = 'list-ordering: enable'
self.check('---\n'
'- hair\n'
'- hais\n'
'- haïr\n'
'- haïssable\n', conf)
self.check('---\n'
'- haïr\n'
'- hais\n', conf,
problem=(3, 3))
def test_locale_case(self):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
self.skipTest('locale en_US.UTF-8 not available')
conf = ('list-ordering: enable')
self.check('---\n'
'- t-shirt\n'
'- T-shirt\n'
'- t-shirts\n'
'- T-shirts\n', conf)
self.check('---\n'
'- t-shirt\n'
'- t-shirts\n'
'- T-shirt\n'
'- T-shirts\n', conf,
problem=(4, 3))
def test_locale_accents(self):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
self.skipTest('locale en_US.UTF-8 not available')
conf = ('list-ordering: enable')
self.check('---\n'
'- hair\n'
'- haïr\n'
'- hais\n'
'- haïssable\n', conf)
self.check('---\n'
'- hais\n'
'- haïr\n', conf,
problem=(3, 3))

@ -25,6 +25,7 @@ rules:
key-duplicates: enable key-duplicates: enable
key-ordering: disable key-ordering: disable
line-length: enable line-length: enable
list-ordering: disable
new-line-at-end-of-file: enable new-line-at-end-of-file: enable
new-lines: enable new-lines: enable
octal-values: disable octal-values: disable

@ -29,6 +29,7 @@ from yamllint.rules import (
key_duplicates, key_duplicates,
key_ordering, key_ordering,
line_length, line_length,
list_ordering,
new_line_at_end_of_file, new_line_at_end_of_file,
new_lines, new_lines,
octal_values, octal_values,
@ -55,6 +56,7 @@ _RULES = {
key_duplicates.ID: key_duplicates, key_duplicates.ID: key_duplicates,
key_ordering.ID: key_ordering, key_ordering.ID: key_ordering,
line_length.ID: line_length, line_length.ID: line_length,
list_ordering.ID: list_ordering,
new_line_at_end_of_file.ID: new_line_at_end_of_file, new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines, new_lines.ID: new_lines,
octal_values.ID: octal_values, octal_values.ID: octal_values,

@ -0,0 +1,119 @@
# Copyright (C) 2023 Johannes F. Knauf and Kevin Wojniak
#
# 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 enforce alphabetical ordering of items in lists. The sorting
order uses the Unicode code point number as a default. As a result, the
ordering is case-sensitive and not accent-friendly (see examples below).
This can be changed by setting the global ``locale`` option. This allows one
to sort case and accents properly.
.. rubric:: Examples
#. With ``list-ordering: {}``
the following code snippets would **PASS**:
::
- key 1
- key 2
- key 3
- [a, b, c]
- T-shirt
- T-shirts
- t-shirt
- t-shirts
- hair
- hais
- haïr
- haïssable
the following code snippets would **FAIL**:
::
- key 2
- key 1
- [b, a]
- T-shirt
- t-shirt
- T-shirts
- t-shirts
- haïr
- hais
#. With global option ``locale: "en_US.UTF-8"`` and rule ``list-ordering: {}``
as opposed to before, the following code snippets would now **PASS**:
::
- t-shirt
- T-shirt
- t-shirts
- T-shirts
- hair
- haïr
- hais
- haïssable
"""
from locale import strcoll
import yaml
from yamllint.linter import LintProblem
ID = 'list-ordering'
TYPE = 'token'
MAP, SEQ = range(2)
class Parent:
def __init__(self, type):
self.type = type
self.items = []
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = []
if isinstance(token, (yaml.BlockMappingStartToken,
yaml.FlowMappingStartToken)):
context['stack'].append(Parent(MAP))
elif isinstance(token, (yaml.BlockSequenceStartToken,
yaml.FlowSequenceStartToken)):
context['stack'].append(Parent(SEQ))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
context['stack'].pop()
elif isinstance(token, yaml.ScalarToken):
if len(context['stack']) > 0 and context['stack'][-1].type == SEQ:
if any(strcoll(token.value, item) < 0
for item in context['stack'][-1].items):
yield LintProblem(
token.start_mark.line + 1, token.start_mark.column + 1,
'wrong list item order "%s"' % token.value)
else:
context['stack'][-1].items.append(token.value)
Loading…
Cancel
Save