diff --git a/tests/rules/test_key_duplicates.py b/tests/rules/test_key_duplicates.py index 3f8a9e62..c1704e68 100644 --- a/tests/rules/test_key_duplicates.py +++ b/tests/rules/test_key_duplicates.py @@ -179,3 +179,57 @@ def test_key_tokens_in_flow_sequences(self): '[\n' ' flow: sequence, with, key: value, mappings\n' ']\n', conf) + + def test_forbid_duplicated_merge_keys(self): + conf = 'key-duplicates: {forbid-duplicated-merge-keys: true}' + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' <<: *anchor_one\n' + ' <<: *anchor_two\n', conf, problem=(9, 3)) + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_three: &anchor_three\n' + ' two: three\n' + 'anchor_reference:\n' + ' <<: *anchor_one\n' + ' <<: *anchor_two\n' + ' <<: *anchor_three\n', conf, + problem1=(11, 3), problem2=(12, 3)) + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' a: 1\n' + ' <<: *anchor_one\n' + ' b: 2\n' + ' <<: *anchor_two\n', conf, problem=(11, 3)) + self.check('---\n' + 'Single Merge Key is OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' <<: [*anchor_one, *anchor_two]\n', conf) + self.check('---\n' + 'Duplicate keys without Merge Keys:\n' + ' key: a\n' + ' otherkey: b\n' + ' key: c\n', conf, + problem=(5, 3)) + self.check('---\n' + 'No Merge Keys:\n' + ' key: a\n' + ' otherkey: b\n', conf) diff --git a/yamllint/rules/key_duplicates.py b/yamllint/rules/key_duplicates.py index 771a8e2a..bc6550f8 100644 --- a/yamllint/rules/key_duplicates.py +++ b/yamllint/rules/key_duplicates.py @@ -16,6 +16,19 @@ """ Use this rule to prevent multiple entries with the same key in mappings. +.. rubric:: Options + +* Use ``forbid-duplicated-merge-keys`` to forbid the usage of + multiple merge keys ``<<``. + +.. rubric:: Default values (when enabled) + +.. code-block:: yaml + + rules: + key-duplicates: + forbid-duplicated-merge-keys: false + .. rubric:: Examples #. With ``key-duplicates: {}`` @@ -51,6 +64,29 @@ other duplication : 2 + +#. With `key-duplicates`: {forbid-duplicated-merge-keys: true}`` + + the following code snippet would **PASS**: + :: + + anchor_one: &anchor_one + one: one + anchor_two: &anchor_two + two: two + anchor_reference: + <<: [*anchor_one, *anchor_two] + + the following code snippet would **FAIL**: + :: + + anchor_one: &anchor_one + one: one + anchor_two: &anchor_two + two: two + anchor_reference: + <<: *anchor_one + <<: *anchor_two """ import yaml @@ -60,6 +96,8 @@ ID = 'key-duplicates' TYPE = 'token' +CONF = {'forbid-duplicated-merge-keys': bool} +DEFAULT = {'forbid-duplicated-merge-keys': False} MAP, SEQ = range(2) @@ -92,7 +130,8 @@ def check(conf, token, prev, next, nextnext, context): if len(context['stack']) > 0 and context['stack'][-1].type == MAP: if (next.value in context['stack'][-1].keys and # `<<` is "merge key", see http://yaml.org/type/merge.html - next.value != '<<'): + (next.value != '<<' or + conf['forbid-duplicated-merge-keys'])): yield LintProblem( next.start_mark.line + 1, next.start_mark.column + 1, f'duplication of key "{next.value}" in mapping')