Skip to content

Commit ca2c962

Browse files
authored
Add avoidEscape option to vue/html-quotes rule (#1031)
1 parent 5980cdc commit ca2c962

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

docs/rules/html-quotes.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,19 @@ Default is set to `double`.
4343

4444
```json
4545
{
46-
"vue/html-quotes": ["error", "double" | "single"]
46+
"vue/html-quotes": [ "error", "double" | "single", { "avoidEscape": false } ]
4747
}
4848
```
4949

50+
String option:
51+
5052
- `"double"` (default) ... requires double quotes.
5153
- `"single"` ... requires single quotes.
5254

55+
Object option:
56+
57+
- `avoidEscape` ... If `true`, allows strings to use single-quotes or double-quotes so long as the string contains a quote that would have to be escaped otherwise.
58+
5359
### `"single"`
5460

5561
<eslint-code-block fix :rules="{'vue/html-quotes': ['error', 'single']}">
@@ -67,6 +73,23 @@ Default is set to `double`.
6773

6874
</eslint-code-block>
6975

76+
### `"double", { "avoidEscape": true }`
77+
78+
<eslint-code-block fix :rules="{'vue/html-quotes': ['error', 'double', { avoidEscape: true }]}">
79+
80+
```vue
81+
<template>
82+
<!-- ✓ GOOD -->
83+
<img title='a string containing "double" quotes'>
84+
85+
<!-- ✗ BAD -->
86+
<img title='foo'>
87+
<img title=bar>
88+
</template>
89+
```
90+
91+
</eslint-code-block>
92+
7093
## :books: Further reading
7194

7295
- [Style guide - Quoted attribute values](https://vuejs.org/v2/style-guide/#Quoted-attribute-values-strongly-recommended)

lib/rules/html-quotes.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,25 @@ module.exports = {
2525
},
2626
fixable: 'code',
2727
schema: [
28-
{ enum: ['double', 'single'] }
28+
{ enum: ['double', 'single'] },
29+
{
30+
type: 'object',
31+
properties: {
32+
avoidEscape: {
33+
type: 'boolean'
34+
}
35+
},
36+
additionalProperties: false
37+
}
2938
]
3039
},
3140

3241
create (context) {
3342
const sourceCode = context.getSourceCode()
3443
const double = context.options[0] !== 'single'
44+
const avoidEscape = context.options[1] && context.options[1].avoidEscape === true
3545
const quoteChar = double ? '"' : "'"
3646
const quoteName = double ? 'double quotes' : 'single quotes'
37-
const quotePattern = double ? /"/g : /'/g
38-
const quoteEscaped = double ? '&quot;' : '&apos;'
3947
let hasInvalidEOF
4048

4149
return utils.defineTemplateBodyVisitor(context, {
@@ -48,14 +56,35 @@ module.exports = {
4856
const firstChar = text[0]
4957

5058
if (firstChar !== quoteChar) {
59+
const quoted = (firstChar === "'" || firstChar === '"')
60+
if (avoidEscape && quoted) {
61+
const contentText = text.slice(1, -1)
62+
if (contentText.includes(quoteChar)) {
63+
return
64+
}
65+
}
66+
5167
context.report({
5268
node: node.value,
5369
loc: node.value.loc,
5470
message: 'Expected to be enclosed by {{kind}}.',
5571
data: { kind: quoteName },
5672
fix (fixer) {
57-
const contentText = (firstChar === "'" || firstChar === '"') ? text.slice(1, -1) : text
58-
const replacement = quoteChar + contentText.replace(quotePattern, quoteEscaped) + quoteChar
73+
const contentText = quoted ? text.slice(1, -1) : text
74+
75+
const fixToDouble = avoidEscape && !quoted && contentText.includes(quoteChar)
76+
? (
77+
double
78+
? contentText.includes("'")
79+
: !contentText.includes('"')
80+
)
81+
: double
82+
83+
const quotePattern = fixToDouble ? /"/g : /'/g
84+
const quoteEscaped = fixToDouble ? '&quot;' : '&apos;'
85+
const fixQuoteChar = fixToDouble ? '"' : "'"
86+
87+
const replacement = fixQuoteChar + contentText.replace(quotePattern, quoteEscaped) + fixQuoteChar
5988
return fixer.replaceText(node.value, replacement)
6089
}
6190
})

tests/lib/rules/html-quotes.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ tester.run('html-quotes', rule, {
5555
code: "<template><div :class='foo'></div></template>",
5656
options: ['single']
5757
},
58+
// avoidEscape
59+
{
60+
filename: 'test.vue',
61+
code: "<template><div attr='foo\"bar'></div></template>",
62+
options: ['double', { avoidEscape: true }]
63+
},
64+
{
65+
filename: 'test.vue',
66+
code: "<template><div attr=\"foo'bar\"></div></template>",
67+
options: ['single', { avoidEscape: true }]
68+
},
5869

5970
// Invalid EOF
6071
{
@@ -166,6 +177,49 @@ tester.run('html-quotes', rule, {
166177
output: "<template><div :class='foo+&apos;bar&apos;'></div></template>",
167178
options: ['single'],
168179
errors: ['Expected to be enclosed by single quotes.']
180+
},
181+
// avoidEscape
182+
{
183+
filename: 'test.vue',
184+
code: "<template><div attr='foo'></div></template>",
185+
output: '<template><div attr="foo"></div></template>',
186+
options: ['double', { avoidEscape: true }],
187+
errors: ['Expected to be enclosed by double quotes.']
188+
},
189+
{
190+
filename: 'test.vue',
191+
code: '<template><div attr="bar"></div></template>',
192+
output: "<template><div attr='bar'></div></template>",
193+
options: ['single', { avoidEscape: true }],
194+
errors: ['Expected to be enclosed by single quotes.']
195+
},
196+
{
197+
filename: 'test.vue',
198+
code: '<template><div attr=foo"bar></div></template>',
199+
output: '<template><div attr=\'foo"bar\'></div></template>',
200+
options: ['double', { avoidEscape: true }],
201+
errors: ['Expected to be enclosed by double quotes.']
202+
},
203+
{
204+
filename: 'test.vue',
205+
code: '<template><div attr=foo\'bar></div></template>',
206+
output: "<template><div attr=\"foo'bar\"></div></template>",
207+
options: ['single', { avoidEscape: true }],
208+
errors: ['Expected to be enclosed by single quotes.']
209+
},
210+
{
211+
filename: 'test.vue',
212+
code: '<template><div attr=foo"bar\'baz></div></template>',
213+
output: '<template><div attr="foo&quot;bar\'baz"></div></template>',
214+
options: ['double', { avoidEscape: true }],
215+
errors: ['Expected to be enclosed by double quotes.']
216+
},
217+
{
218+
filename: 'test.vue',
219+
code: '<template><div attr=foo"bar\'baz></div></template>',
220+
output: '<template><div attr=\'foo"bar&apos;baz\'></div></template>',
221+
options: ['single', { avoidEscape: true }],
222+
errors: ['Expected to be enclosed by single quotes.']
169223
}
170224
]
171225
})

0 commit comments

Comments
 (0)