3030 ``required: false`` and ``required: only-when-needed``.
3131* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
3232 even if ``required: only-when-needed`` is set.
33+ * ``allow-double-quotes-for-escaping` defines whether or not to allow
34+ double quotes even if ``quote-type: single`` or
35+ ``required: only-when-needed`` when the quoted
36+ string contains escape sequences.
3337* ``allow-quoted-quotes`` allows (``true``) using disallowed quotes for strings
3438 with allowed quotes inside. Default ``false``.
3539* ``check-keys`` defines whether to apply the rules to keys in mappings. By
165169 ::
166170
167171 "foo:bar": baz
172+ #. With ``quoted-strings: {quote-type: singe,
173+ allow-double-quotes-for-escaping: true}``
174+
175+ the following code snippet would **PASS**:
176+ ::
177+
178+ foo: 'bar'
179+ baz: "quux\\ nquux 2"
180+
181+ the following code snippet would **FAIL**:
182+ ::
183+
184+ foo: 'bar'
185+ baz: "quux quux 2"
168186"""
169187
170188import re
175193
176194ID = 'quoted-strings'
177195TYPE = 'token'
178- CONF = {'quote-type' : ('any' , 'single' , 'double' , 'consistent' ),
196+ CONF = {'quote-type' :
197+ (
198+ 'any' ,
199+ 'single' ,
200+ 'double' ,
201+ 'consistent' ,
202+ ),
179203 'required' : (True , False , 'only-when-needed' ),
180204 'extra-required' : [str ],
181205 'extra-allowed' : [str ],
206+ 'allow-double-quotes-for-escaping' : bool ,
182207 'allow-quoted-quotes' : bool ,
183208 'check-keys' : bool }
184209DEFAULT = {'quote-type' : 'any' ,
185210 'required' : True ,
186211 'extra-required' : [],
187212 'extra-allowed' : [],
213+ 'allow-double-quotes-for-escaping' : False ,
188214 'allow-quoted-quotes' : False ,
189215 'check-keys' : False }
190216
@@ -212,7 +238,9 @@ def VALIDATE(conf):
212238 list ('-+0123456789' ))
213239
214240
215- def _quote_match (quote_type , token_style , context ):
241+ def _quote_match (quote_type , token , context ):
242+ token_style = token .style
243+
216244 if quote_type == 'consistent' and token_style is not None :
217245 # The canonical token style in a document is assumed to be the first
218246 # one found for the purpose of 'consistent'
@@ -263,6 +291,17 @@ def _has_quoted_quotes(token):
263291 (token .style == '"' and "'" in token .value )))
264292
265293
294+ def _has_escaping_in_double_quotes (token ):
295+ if token .style != '"' :
296+ return False
297+
298+ plain_value = token .start_mark .buffer [
299+ token .start_mark .pointer :token .end_mark .pointer
300+ ]
301+
302+ return ('\\ ' in plain_value )
303+
304+
266305def _has_backslash_on_at_least_one_line_ending (token ):
267306 if token .start_mark .line == token .end_mark .line :
268307 return False
@@ -315,17 +354,21 @@ def check(conf, token, prev, next, nextnext, context):
315354
316355 # Quotes are mandatory and need to match config
317356 if (token .style is None or
318- not (_quote_match (quote_type , token .style , context ) or
319- (conf ['allow-quoted-quotes' ] and _has_quoted_quotes (token )))):
357+ not (_quote_match (quote_type , token , context ) or
358+ (conf ['allow-quoted-quotes' ] and _has_quoted_quotes (token )) or
359+ (conf ['allow-double-quotes-for-escaping' ] and
360+ _has_escaping_in_double_quotes (token )))):
320361 msg = f"string { node } is not quoted with { quote_type } quotes"
321362
322363 elif conf ['required' ] is False :
323364
324365 # Quotes are not mandatory but when used need to match config
325366 if (token .style and
326- not _quote_match (quote_type , token . style , context ) and
367+ not _quote_match (quote_type , token , context ) and
327368 not (conf ['allow-quoted-quotes' ] and
328- _has_quoted_quotes (token ))):
369+ _has_quoted_quotes (token )) and
370+ not (conf ['allow-double-quotes-for-escaping' ] and
371+ _has_escaping_in_double_quotes (token ))):
329372 msg = f"string { node } is not quoted with { quote_type } quotes"
330373
331374 elif not token .style :
@@ -343,13 +386,14 @@ def check(conf, token, prev, next, nextnext, context):
343386 for r in conf ['extra-required' ])
344387 is_extra_allowed = any (re .search (r , token .value )
345388 for r in conf ['extra-allowed' ])
346- if not (is_extra_required or is_extra_allowed ):
389+ contains_escape = _has_escaping_in_double_quotes (token )
390+ if not (is_extra_required or is_extra_allowed or contains_escape ):
347391 msg = (f"string { node } is redundantly quoted with "
348392 f"{ quote_type } quotes" )
349393
350394 # But when used need to match config
351395 elif (token .style and
352- not _quote_match (quote_type , token . style , context ) and
396+ not _quote_match (quote_type , token , context ) and
353397 not (conf ['allow-quoted-quotes' ] and _has_quoted_quotes (token ))):
354398 msg = f"string { node } is not quoted with { quote_type } quotes"
355399
0 commit comments