2020
2121.. rubric:: Options
2222
23- * ``quote-type`` defines allowed quotes: ``single``, ``double`` or ``any``
23+ * ``quote-type`` defines allowed quotes: ``single``, ``double``,
24+ ``single-unless-contains-escaped``, or ``any``
2425 (default).
2526* ``required`` defines whether using quotes in string values is required
2627 (``true``, default) or not (``false``), or only allowed when really needed
165166 ::
166167
167168 "foo:bar": baz
169+ #. With ``quoted-strings: {quote-type: single-unless-contains-escaped}``
170+
171+ the following code snippet would **PASS**:
172+ ::
173+
174+ foo: 'bar'
175+ baz: "quux\\ nquux 2"
176+
177+ the following code snippet would **FAIL**:
178+ ::
179+
180+ foo: 'bar'
181+ baz: "quux quux 2"
168182"""
169183
170184import re
175189
176190ID = 'quoted-strings'
177191TYPE = 'token'
178- CONF = {'quote-type' : ('any' , 'single' , 'double' , 'consistent' ),
192+ CONF = {'quote-type' :
193+ (
194+ 'any' ,
195+ 'single' ,
196+ 'double' ,
197+ 'consistent' ,
198+ 'single-unless-contains-escaped'
199+ ),
179200 'required' : (True , False , 'only-when-needed' ),
180201 'extra-required' : [str ],
181202 'extra-allowed' : [str ],
@@ -212,14 +233,24 @@ def VALIDATE(conf):
212233 list ('-+0123456789' ))
213234
214235
215- def _quote_match (quote_type , token_style , context ):
236+ def _quote_match (quote_type , token , context ):
237+ token_style = token .style
238+
216239 if quote_type == 'consistent' and token_style is not None :
217240 # The canonical token style in a document is assumed to be the first
218241 # one found for the purpose of 'consistent'
219242 if 'quoted_strings_consistent_token_style' not in context :
220243 context ['quoted_strings_consistent_token_style' ] = token_style
221244 return context ['quoted_strings_consistent_token_style' ] == token_style
222245
246+ if (quote_type == 'single-unless-contains-escaped' and
247+ token_style is not None ):
248+ plain_value = token .start_mark .buffer [
249+ token .start_mark .pointer :token .end_mark .pointer
250+ ]
251+ return ((token_style == "'" ) or
252+ (token_style == '"' and '\\ ' in plain_value ))
253+
223254 return ((quote_type == 'any' ) or
224255 (quote_type == 'single' and token_style == "'" ) or
225256 (quote_type == 'double' and token_style == '"' ))
@@ -315,15 +346,15 @@ def check(conf, token, prev, next, nextnext, context):
315346
316347 # Quotes are mandatory and need to match config
317348 if (token .style is None or
318- not (_quote_match (quote_type , token . style , context ) or
349+ not (_quote_match (quote_type , token , context ) or
319350 (conf ['allow-quoted-quotes' ] and _has_quoted_quotes (token )))):
320351 msg = f"string { node } is not quoted with { quote_type } quotes"
321352
322353 elif conf ['required' ] is False :
323354
324355 # Quotes are not mandatory but when used need to match config
325356 if (token .style and
326- not _quote_match (quote_type , token . style , context ) and
357+ not _quote_match (quote_type , token , context ) and
327358 not (conf ['allow-quoted-quotes' ] and
328359 _has_quoted_quotes (token ))):
329360 msg = f"string { node } is not quoted with { quote_type } quotes"
@@ -349,7 +380,7 @@ def check(conf, token, prev, next, nextnext, context):
349380
350381 # But when used need to match config
351382 elif (token .style and
352- not _quote_match (quote_type , token . style , context ) and
383+ not _quote_match (quote_type , token , context ) and
353384 not (conf ['allow-quoted-quotes' ] and _has_quoted_quotes (token ))):
354385 msg = f"string { node } is not quoted with { quote_type } quotes"
355386
0 commit comments