Skip to content

Commit ab0473b

Browse files
authored
Fail on duplicate YAML keys (#51646)
Fixes: #51638
1 parent f9eb9c3 commit ab0473b

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

tools/lint/tests/test_file_lints.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,34 @@ def test_invalid_web_features_file():
12361236
assert errors == []
12371237

12381238

1239+
def test_duplicate_keys_invalid_web_features_file():
1240+
code = b"""\
1241+
features:
1242+
- name: feature1
1243+
files:
1244+
- feature1-*
1245+
features:
1246+
- name: feature2
1247+
files:
1248+
- "feature2-*"
1249+
"""
1250+
# Check when the value is named correctly. It should find the error.
1251+
errors = check_file_contents("", "css/WEB_FEATURES.yml", io.BytesIO(code))
1252+
check_errors(errors)
1253+
1254+
assert errors == [
1255+
('INVALID-WEB-FEATURES-FILE',
1256+
'The WEB_FEATURES.yml file contains an invalid structure',
1257+
"css/WEB_FEATURES.yml",
1258+
None),
1259+
]
1260+
1261+
# Check when the value is named incorrectly. It should not find the error.
1262+
errors = check_file_contents("", "css/OTHER_WEB_FEATURES.yml", io.BytesIO(code))
1263+
check_errors(errors)
1264+
1265+
assert errors == []
1266+
12391267
def test_css_missing_file_manual():
12401268
errors = check_file_contents("", "css/foo/bar-manual.html", io.BytesIO(b""))
12411269
check_errors(errors)

tools/metadata/yaml/load.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,24 @@
33

44
import yaml
55

6+
# PyYaml does not currently handle unique keys.
7+
# https://github.com/yaml/pyyaml/issues/165#issuecomment-430074049
8+
# In that issue, there are workarounds to it.
9+
# https://gist.github.com/pypt/94d747fe5180851196eb?permalink_comment_id=4015118#gistcomment-4015118
10+
11+
class UniqueKeyLoader(yaml.SafeLoader):
12+
def construct_mapping(self, node: yaml.MappingNode, deep: bool = False) -> Dict[Any, Any]:
13+
mapping = set()
14+
for key_node, value_node in node.value:
15+
key = self.construct_object(key_node, deep=deep) # type: ignore
16+
if key in mapping:
17+
raise ValueError(f"Duplicate {key!r} key found in YAML.")
18+
mapping.add(key)
19+
return super().construct_mapping(node, deep)
20+
621
def load_data_to_dict(f: IO[bytes]) -> Dict[str, Any]:
722
try:
8-
raw_data = yaml.safe_load(f)
23+
raw_data = yaml.load(f, Loader=UniqueKeyLoader)
924
return SchemaValue.from_dict(raw_data)
1025
except Exception as e:
1126
raise e

0 commit comments

Comments
 (0)