Skip to content

Commit e3d54cc

Browse files
rfinnieadrienverge
authored andcommitted
quoted-strings: Add quote-type: consistent
Strings in a document may be single or double, but must be consistent. The first string found is assumed to be the canonical quote type, and any subsequent quotes will be compared to it. Closes: #763
1 parent 0b4ddc8 commit e3d54cc

File tree

2 files changed

+67
-5
lines changed

2 files changed

+67
-5
lines changed

tests/rules/test_quoted_strings.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,47 @@ def test_quote_type_double(self):
186186
' word 2"\n',
187187
conf, problem1=(9, 3))
188188

189+
def test_quote_type_consistent(self):
190+
conf = 'quoted-strings: {quote-type: consistent}'
191+
self.check('---\n'
192+
'string1: "foo"\n'
193+
'string2: "bar"\n'
194+
'string3: \'baz\'\n' # fails
195+
'string4: "quux"\n',
196+
conf, problem1=(4, 10))
197+
198+
conf = ('quoted-strings:\n'
199+
' quote-type: consistent\n'
200+
' check-keys: true\n')
201+
self.check('---\n'
202+
'"string1": "foo"\n'
203+
'string2: "bar"\n' # fails
204+
'\'string3\': "baz"\n' # fails
205+
'"string4": {"key": "val"}\n'
206+
'"string5": {\'key\': "val"}\n', # fails
207+
conf, problem1=(3, 1), problem2=(4, 1), problem3=(6, 13))
208+
209+
conf = ('quoted-strings:\n'
210+
' quote-type: consistent\n'
211+
' check-keys: true\n'
212+
' required: false\n')
213+
self.check('---\n'
214+
'string1: \'foo\'\n'
215+
'string2: "bar"\n' # fails
216+
'string3: \'baz\'\n'
217+
'string4: {\'key\': "val"}\n' # fails
218+
'string5: {"key": \'val\'}\n' # fails
219+
'string6:\n'
220+
' \'key\': "val"\n' # fails
221+
'string7:\n'
222+
' "key": \'val\'\n' # fails
223+
'string8:\n'
224+
' "string"\n' # fails
225+
'string9: >\n'
226+
' "string"\n',
227+
conf, problem1=(3, 10), problem2=(5, 18), problem3=(6, 11),
228+
problem4=(8, 10), problem5=(10, 3), problem6=(12, 3))
229+
189230
def test_any_quotes_not_required(self):
190231
conf = 'quoted-strings: {quote-type: any, required: false}\n'
191232

yamllint/rules/quoted_strings.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@
139139
140140
foo: 'bar"baz'
141141
142+
#. With ``quoted-strings: {quote-type: consistent}``
143+
144+
the following code snippet would **PASS**:
145+
::
146+
147+
foo: 'bar'
148+
baz: 'quux'
149+
150+
the following code snippet would **FAIL**:
151+
::
152+
153+
foo: 'bar'
154+
baz: "quux"
155+
142156
#. With ``quoted-strings: {required: only-when-needed, check-keys: true,
143157
extra-required: ["[:]"]}``
144158
@@ -161,7 +175,7 @@
161175

162176
ID = 'quoted-strings'
163177
TYPE = 'token'
164-
CONF = {'quote-type': ('any', 'single', 'double'),
178+
CONF = {'quote-type': ('any', 'single', 'double', 'consistent'),
165179
'required': (True, False, 'only-when-needed'),
166180
'extra-required': [str],
167181
'extra-allowed': [str],
@@ -198,7 +212,14 @@ def VALIDATE(conf):
198212
list('-+0123456789'))
199213

200214

201-
def _quote_match(quote_type, token_style):
215+
def _quote_match(quote_type, token_style, context):
216+
if quote_type == 'consistent' and token_style is not None:
217+
# The canonical token style in a document is assumed to be the first
218+
# one found for the purpose of 'consistent'
219+
if 'quoted_strings_consistent_token_style' not in context:
220+
context['quoted_strings_consistent_token_style'] = token_style
221+
return context['quoted_strings_consistent_token_style'] == token_style
222+
202223
return ((quote_type == 'any') or
203224
(quote_type == 'single' and token_style == "'") or
204225
(quote_type == 'double' and token_style == '"'))
@@ -294,15 +315,15 @@ def check(conf, token, prev, next, nextnext, context):
294315

295316
# Quotes are mandatory and need to match config
296317
if (token.style is None or
297-
not (_quote_match(quote_type, token.style) or
318+
not (_quote_match(quote_type, token.style, context) or
298319
(conf['allow-quoted-quotes'] and _has_quoted_quotes(token)))):
299320
msg = f"string {node} is not quoted with {quote_type} quotes"
300321

301322
elif conf['required'] is False:
302323

303324
# Quotes are not mandatory but when used need to match config
304325
if (token.style and
305-
not _quote_match(quote_type, token.style) and
326+
not _quote_match(quote_type, token.style, context) and
306327
not (conf['allow-quoted-quotes'] and
307328
_has_quoted_quotes(token))):
308329
msg = f"string {node} is not quoted with {quote_type} quotes"
@@ -328,7 +349,7 @@ def check(conf, token, prev, next, nextnext, context):
328349

329350
# But when used need to match config
330351
elif (token.style and
331-
not _quote_match(quote_type, token.style) and
352+
not _quote_match(quote_type, token.style, context) and
332353
not (conf['allow-quoted-quotes'] and _has_quoted_quotes(token))):
333354
msg = f"string {node} is not quoted with {quote_type} quotes"
334355

0 commit comments

Comments
 (0)