diff --git a/docs/rules.rst b/docs/rules.rst
index c030c3d..eb3bc82 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -14,6 +14,11 @@ This page describes the rules and their options.
:local:
:depth: 1
+anchors
+-------
+
+.. automodule:: yamllint.rules.anchors
+
braces
------
diff --git a/tests/rules/test_anchors.py b/tests/rules/test_anchors.py
new file mode 100644
index 0000000..f0e6581
--- /dev/null
+++ b/tests/rules/test_anchors.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2021 Sergei Mikhailov
+#
+# 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
+
+
+HIT_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *keyanchor
+'''
+MISS_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *missedkeyanchor
+'''
+
+HIT_ANCHOR_MERGE = '''---
+block: &keyanchor
+ key: a
+otherkey:
+ <<: *keyanchor
+ otherkey: b
+'''
+MISS_ANCHOR_MERGE = '''---
+block: &keyanchor
+ key: a
+otherkey:
+ <<: *missedkeyanchor
+ otherkey: b
+'''
+
+MULTI_HIT_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *keyanchor
+otherotherkey: *keyanchor
+'''
+MULTI_MISS_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *missedkeyanchor
+otherotherkey: *missedkeyanchor
+'''
+
+MULTI_DOC_HIT_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *keyanchor
+---
+key: &otherkeyanchor a
+otherkey: *otherkeyanchor
+'''
+MULTI_DOC_MISS_ANCHOR_POINTER = '''---
+key: &keyanchor a
+otherkey: *missedkeyanchor
+---
+key: &otherkeyanchor a
+otherkey: *othermissedkeyanchor
+'''
+
+DUPLICATE_ANCHORS = '''---
+first_block: &keyanchor
+ key: a
+
+second_block: &keyanchor
+ key: b
+
+target_block: *keyanchor
+'''
+
+
+class AnchorsTestCase(RuleTestCase):
+ rule_id = 'anchors'
+
+ def test_disabled(self):
+ conf = 'anchors: disable'
+
+ self.check(HIT_ANCHOR_POINTER, conf)
+ self.check(HIT_ANCHOR_MERGE, conf)
+ self.check(MULTI_HIT_ANCHOR_POINTER, conf)
+ self.check(MULTI_DOC_HIT_ANCHOR_POINTER, conf)
+
+ self.check(MISS_ANCHOR_POINTER, conf)
+ self.check(MISS_ANCHOR_MERGE, conf)
+ self.check(MULTI_MISS_ANCHOR_POINTER, conf)
+ self.check(MULTI_DOC_MISS_ANCHOR_POINTER, conf)
+ self.check(DUPLICATE_ANCHORS, conf)
+
+ def test_enabled(self):
+ conf = 'anchors: enable'
+
+ self.check(HIT_ANCHOR_POINTER, conf)
+ self.check(HIT_ANCHOR_MERGE, conf)
+ self.check(MULTI_HIT_ANCHOR_POINTER, conf)
+ self.check(MULTI_DOC_HIT_ANCHOR_POINTER, conf)
+
+ self.check(MISS_ANCHOR_POINTER, conf,
+ problem=(3, 11))
+ self.check(MISS_ANCHOR_MERGE, conf,
+ problem_first=(5, 7),
+ problem_second=(5, 7))
+ self.check(MULTI_MISS_ANCHOR_POINTER, conf,
+ problem_first=(3, 11),
+ problem_second=(4, 16))
+ self.check(MULTI_DOC_MISS_ANCHOR_POINTER, conf,
+ problem_first=(3, 11), problem_second=(6, 11))
+ self.check(DUPLICATE_ANCHORS, conf,
+ problem=(5, 15))
diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml
index 0dea0aa..b082e22 100644
--- a/yamllint/conf/default.yaml
+++ b/yamllint/conf/default.yaml
@@ -6,6 +6,7 @@ yaml-files:
- '.yamllint'
rules:
+ anchors: enable
braces: enable
brackets: enable
colons: enable
diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py
index 5a4fe81..6b5e446 100644
--- a/yamllint/rules/__init__.py
+++ b/yamllint/rules/__init__.py
@@ -14,6 +14,7 @@
# along with this program. If not, see .
from yamllint.rules import (
+ anchors,
braces,
brackets,
colons,
@@ -39,6 +40,7 @@ from yamllint.rules import (
)
_RULES = {
+ anchors.ID: anchors,
braces.ID: braces,
brackets.ID: brackets,
colons.ID: colons,
diff --git a/yamllint/rules/anchors.py b/yamllint/rules/anchors.py
new file mode 100644
index 0000000..23f48fd
--- /dev/null
+++ b/yamllint/rules/anchors.py
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2021 Sergei Mikhailov
+#
+# 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 aliases pointing to pointers that don't exist.
+
+.. rubric:: Examples
+
+#. With ``anchors: {}``
+
+ the following code snippet would **PASS**:
+ ::
+
+ address: &address |
+ Williams St. 13
+ target_address: *address
+
+ the following code snippet would **FAIL**:
+ ::
+
+ address: &address |
+ Williams St. 13
+ target_address: *wrong_address
+
+#. This appends to merges as well:
+
+ the following code snippet would **PASS**:
+ ::
+
+ default_address: &address
+ state: South Carolina
+ city: Barnwell
+ target_address:
+ <<: *address
+ city: Barnaul
+
+ the following code snippet would **FAIL**:
+ ::
+
+ default_address: &address
+ state: South Carolina
+ city: Barnwell
+ target_address:
+ <<: *wrong_address
+ city: Barnaul
+
+#. Duplicate anchors are not allowed.
+
+ the following code snippet would **PASS**:
+ ::
+
+ clients:
+ jack: &jack_client
+ billing_id: 1234
+ bill: &bill_client
+ billing_id: 5678
+ target_client: *jack_client
+
+ the following code snippet would **FAIL**:
+ ::
+
+ clients:
+ jack: &jack_client
+ billing_id: 1234
+ bill: &jack_client
+ billing_id: 5678
+ target_client: *jack_client
+"""
+
+from yaml import DocumentStartToken, AnchorToken, AliasToken, BlockEndToken
+
+from yamllint.linter import LintProblem
+
+
+ID = 'anchors'
+TYPE = 'token'
+
+
+def check(__conf, token, __prev, __next, __nextnext, context):
+ if isinstance(token, (DocumentStartToken)):
+ context['anchors'] = set()
+ context['aliases'] = {}
+ elif ('anchors' in context and 'aliases' in context):
+ if isinstance(token, (AnchorToken)):
+ if token.value in context['anchors']:
+ yield LintProblem(
+ token.start_mark.line + 1, token.start_mark.column + 1,
+ f"anchor '{token.value}' duplicates in document")
+ context['anchors'].add(token.value)
+ elif isinstance(token, (AliasToken)):
+ if token.value not in context['aliases']:
+ context['aliases'][token.value] = []
+ alias_obj = {"line": token.start_mark.line,
+ "column": token.start_mark.column}
+ context['aliases'][token.value].append(alias_obj)
+ elif isinstance(token, (BlockEndToken)):
+ missing_anchors = [alias for alias in list(context['aliases'])
+ if alias not in context['anchors']]
+ if len(missing_anchors) > 0:
+ for miss_anchor in missing_anchors:
+ for location in context['aliases'][miss_anchor]:
+ yield LintProblem(
+ location['line'] + 1, location['column'] + 1,
+ f"anchor '{miss_anchor}' is not found in document")