Skip to content

Commit 0765b0a

Browse files
committed
truthy: Adapt valid values based on YAML version
Specification of YAML ≤ 1.1 has 22 boolean values: y | Y | n | N yes | Yes | YES | no | No | NO true | True | TRUE | false | False | FALSE on | On | ON | off | Off | OFF Whereas YAML 1.2 spec recognizes only 6 [^1]: true | True | TRUE | false | False | FALSE For documents that explicit state their YAML spec version at the top of the document, let's adapt the list of forbidden values. In the future, we should: - implement a configuration option to declare the default YAML spec version, e.g. `default-yaml-spec-version: 1.2`, - consider making 1.2 the default in a future release (this would be a slight breaking change, but yamllint always tried to be 1.2-compatible). - consider adapting yamllint to other 1.1 vs. 1.2 differences [^2]. Solves: #587 Related to: #559 #540 #430 #344 #247 #232 #158 [^1]: https://yaml.org/spec/1.2.2/#1032-tag-resolution [^2]: https://yaml.org/spec/1.2.2/ext/changes/#changes-in-version-12-revision-120-2009-07-21
1 parent 9931ad6 commit 0765b0a

File tree

2 files changed

+127
-18
lines changed

2 files changed

+127
-18
lines changed

tests/rules/test_truthy.py

+80-8
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ def test_disabled(self):
2727
'True: 1\n', conf)
2828

2929
def test_enabled(self):
30-
conf = 'truthy: enable\n'
30+
conf = ('truthy: enable\n'
31+
'document-start: disable\n')
3132
self.check('---\n'
3233
'1: True\n'
3334
'True: 1\n',
3435
conf, problem1=(2, 4), problem2=(3, 1))
3536
self.check('---\n'
3637
'1: "True"\n'
3738
'"True": 1\n', conf)
38-
self.check('---\n'
39+
self.check('%YAML 1.1\n'
40+
'---\n'
3941
'[\n'
4042
' true, false,\n'
4143
' "false", "FALSE",\n'
@@ -44,9 +46,47 @@ def test_enabled(self):
4446
' on, OFF,\n'
4547
' NO, Yes\n'
4648
']\n', conf,
47-
problem1=(6, 3), problem2=(6, 9),
48-
problem3=(7, 3), problem4=(7, 7),
49-
problem5=(8, 3), problem6=(8, 7))
49+
problem1=(7, 3), problem2=(7, 9),
50+
problem3=(8, 3), problem4=(8, 7),
51+
problem5=(9, 3), problem6=(9, 7))
52+
self.check('y: 1\n'
53+
'yes: 2\n'
54+
'on: 3\n'
55+
'true: 4\n'
56+
'True: 5\n'
57+
'...\n'
58+
'%YAML 1.2\n'
59+
'---\n'
60+
'y: 1\n'
61+
'yes: 2\n'
62+
'on: 3\n'
63+
'true: 4\n'
64+
'True: 5\n'
65+
'...\n'
66+
'%YAML 1.1\n'
67+
'---\n'
68+
'y: 1\n'
69+
'yes: 2\n'
70+
'on: 3\n'
71+
'true: 4\n'
72+
'True: 5\n'
73+
'---\n'
74+
'y: 1\n'
75+
'yes: 2\n'
76+
'on: 3\n'
77+
'true: 4\n'
78+
'True: 5\n',
79+
conf,
80+
problem1=(2, 1),
81+
problem2=(3, 1),
82+
problem3=(5, 1),
83+
problem4=(13, 1),
84+
problem5=(18, 1),
85+
problem6=(19, 1),
86+
problem7=(21, 1),
87+
problem8=(24, 1),
88+
problem9=(25, 1),
89+
problem10=(27, 1))
5090

5191
def test_different_allowed_values(self):
5292
conf = ('truthy:\n'
@@ -56,15 +96,16 @@ def test_different_allowed_values(self):
5696
'key2: yes\n'
5797
'key3: bar\n'
5898
'key4: no\n', conf)
59-
self.check('---\n'
99+
self.check('%YAML 1.1\n'
100+
'---\n'
60101
'key1: true\n'
61102
'key2: Yes\n'
62103
'key3: false\n'
63104
'key4: no\n'
64105
'key5: yes\n',
65106
conf,
66-
problem1=(2, 7), problem2=(3, 7),
67-
problem3=(4, 7))
107+
problem1=(3, 7), problem2=(4, 7),
108+
problem3=(5, 7))
68109

69110
def test_combined_allowed_values(self):
70111
conf = ('truthy:\n'
@@ -81,6 +122,22 @@ def test_combined_allowed_values(self):
81122
'key4: no\n'
82123
'key5: yes\n',
83124
conf, problem1=(3, 7))
125+
self.check('%YAML 1.1\n'
126+
'---\n'
127+
'key1: true\n'
128+
'key2: Yes\n'
129+
'key3: false\n'
130+
'key4: no\n'
131+
'key5: yes\n',
132+
conf, problem1=(4, 7))
133+
self.check('%YAML 1.2\n'
134+
'---\n'
135+
'key1: true\n'
136+
'key2: Yes\n'
137+
'key3: false\n'
138+
'key4: no\n'
139+
'key5: yes\n',
140+
conf)
84141

85142
def test_no_allowed_values(self):
86143
conf = ('truthy:\n'
@@ -95,6 +152,21 @@ def test_no_allowed_values(self):
95152
'key4: no\n', conf,
96153
problem1=(2, 7), problem2=(3, 7),
97154
problem3=(4, 7), problem4=(5, 7))
155+
self.check('%YAML 1.1\n'
156+
'---\n'
157+
'key1: true\n'
158+
'key2: yes\n'
159+
'key3: false\n'
160+
'key4: no\n', conf,
161+
problem1=(3, 7), problem2=(4, 7),
162+
problem3=(5, 7), problem4=(6, 7))
163+
self.check('%YAML 1.2\n'
164+
'---\n'
165+
'key1: true\n'
166+
'key2: yes\n'
167+
'key3: false\n'
168+
'key4: no\n', conf,
169+
problem1=(3, 7), problem2=(5, 7))
98170

99171
def test_explicit_types(self):
100172
conf = 'truthy: enable\n'

yamllint/rules/truthy.py

+47-10
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
``[yes, FALSE, Off]`` into ``[true, false, false]`` or
2222
``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``.
2323
24+
Depending on the YAML specification version used by the YAML document, the list
25+
of truthy values can differ. In YAML 1.2, only capitalized / uppercased
26+
combinations of ``true`` and ``false`` are considered truthy, whereas in YAML
27+
1.1 combinations of ``yes``, ``no``, ``on`` and ``off`` are too. To make the
28+
YAML specification version explicit in a YAML document, a ``%YAML 1.2``
29+
directive can be used (see example below).
30+
2431
.. rubric:: Options
2532
2633
* ``allowed-values`` defines the list of truthy values which will be ignored
@@ -80,10 +87,21 @@
8087
the following code snippet would **FAIL**:
8188
::
8289
90+
%YAML 1.1
91+
---
8392
yes: 1
8493
on: 2
8594
True: 3
8695
96+
the following code snippet would **PASS**:
97+
::
98+
99+
%YAML 1.2
100+
---
101+
yes: 1
102+
on: 2
103+
true: 3
104+
87105
#. With ``truthy: {allowed-values: ["yes", "no"]}``
88106
89107
the following code snippet would **PASS**:
@@ -125,31 +143,50 @@
125143

126144
from yamllint.linter import LintProblem
127145

128-
TRUTHY = ['YES', 'Yes', 'yes',
129-
'NO', 'No', 'no',
130-
'TRUE', 'True', 'true',
131-
'FALSE', 'False', 'false',
132-
'ON', 'On', 'on',
133-
'OFF', 'Off', 'off']
146+
TRUTHY_1_1 = ['YES', 'Yes', 'yes',
147+
'NO', 'No', 'no',
148+
'TRUE', 'True', 'true',
149+
'FALSE', 'False', 'false',
150+
'ON', 'On', 'on',
151+
'OFF', 'Off', 'off']
152+
TRUTHY_1_2 = ['TRUE', 'True', 'true',
153+
'FALSE', 'False', 'false']
134154

135155

136156
ID = 'truthy'
137157
TYPE = 'token'
138-
CONF = {'allowed-values': TRUTHY.copy(), 'check-keys': bool}
158+
CONF = {'allowed-values': TRUTHY_1_1.copy(), 'check-keys': bool}
139159
DEFAULT = {'allowed-values': ['true', 'false'], 'check-keys': True}
140160

141161

162+
def yaml_spec_version_for_document(context):
163+
if 'yaml_spec_version' in context:
164+
return context['yaml_spec_version']
165+
return (1, 1)
166+
167+
142168
def check(conf, token, prev, next, nextnext, context):
169+
if isinstance(token, yaml.tokens.DirectiveToken) and token.name == 'YAML':
170+
context['yaml_spec_version'] = token.value
171+
elif isinstance(token, yaml.tokens.DocumentEndToken):
172+
context.pop('yaml_spec_version', None)
173+
context.pop('bad_truthy_values', None)
174+
143175
if prev and isinstance(prev, yaml.tokens.TagToken):
144176
return
145177

146178
if (not conf['check-keys'] and isinstance(prev, yaml.tokens.KeyToken) and
147179
isinstance(token, yaml.tokens.ScalarToken)):
148180
return
149181

150-
if isinstance(token, yaml.tokens.ScalarToken):
151-
if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and
152-
token.style is None):
182+
if isinstance(token, yaml.tokens.ScalarToken) and token.style is None:
183+
if 'bad_truthy_values' not in context:
184+
context['bad_truthy_values'] = set(
185+
TRUTHY_1_2 if yaml_spec_version_for_document(context) == (1, 2)
186+
else TRUTHY_1_1)
187+
context['bad_truthy_values'] -= set(conf['allowed-values'])
188+
189+
if token.value in context['bad_truthy_values']:
153190
yield LintProblem(token.start_mark.line + 1,
154191
token.start_mark.column + 1,
155192
"truthy value should be one of [" +

0 commit comments

Comments
 (0)