Skip to content

Commit

Permalink
Add avoidEscape option to vue/html-quotes rule (#1031)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi authored Feb 16, 2020
1 parent 5980cdc commit ca2c962
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 6 deletions.
25 changes: 24 additions & 1 deletion docs/rules/html-quotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,19 @@ Default is set to `double`.

```json
{
"vue/html-quotes": ["error", "double" | "single"]
"vue/html-quotes": [ "error", "double" | "single", { "avoidEscape": false } ]
}
```

String option:

- `"double"` (default) ... requires double quotes.
- `"single"` ... requires single quotes.

Object option:

- `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.

### `"single"`

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

</eslint-code-block>

### `"double", { "avoidEscape": true }`

<eslint-code-block fix :rules="{'vue/html-quotes': ['error', 'double', { avoidEscape: true }]}">

```vue
<template>
<!-- ✓ GOOD -->
<img title='a string containing "double" quotes'>
<!-- ✗ BAD -->
<img title='foo'>
<img title=bar>
</template>
```

</eslint-code-block>

## :books: Further reading

- [Style guide - Quoted attribute values](https://vuejs.org/v2/style-guide/#Quoted-attribute-values-strongly-recommended)
Expand Down
39 changes: 34 additions & 5 deletions lib/rules/html-quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,25 @@ module.exports = {
},
fixable: 'code',
schema: [
{ enum: ['double', 'single'] }
{ enum: ['double', 'single'] },
{
type: 'object',
properties: {
avoidEscape: {
type: 'boolean'
}
},
additionalProperties: false
}
]
},

create (context) {
const sourceCode = context.getSourceCode()
const double = context.options[0] !== 'single'
const avoidEscape = context.options[1] && context.options[1].avoidEscape === true
const quoteChar = double ? '"' : "'"
const quoteName = double ? 'double quotes' : 'single quotes'
const quotePattern = double ? /"/g : /'/g
const quoteEscaped = double ? '&quot;' : '&apos;'
let hasInvalidEOF

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

if (firstChar !== quoteChar) {
const quoted = (firstChar === "'" || firstChar === '"')
if (avoidEscape && quoted) {
const contentText = text.slice(1, -1)
if (contentText.includes(quoteChar)) {
return
}
}

context.report({
node: node.value,
loc: node.value.loc,
message: 'Expected to be enclosed by {{kind}}.',
data: { kind: quoteName },
fix (fixer) {
const contentText = (firstChar === "'" || firstChar === '"') ? text.slice(1, -1) : text
const replacement = quoteChar + contentText.replace(quotePattern, quoteEscaped) + quoteChar
const contentText = quoted ? text.slice(1, -1) : text

const fixToDouble = avoidEscape && !quoted && contentText.includes(quoteChar)
? (
double
? contentText.includes("'")
: !contentText.includes('"')
)
: double

const quotePattern = fixToDouble ? /"/g : /'/g
const quoteEscaped = fixToDouble ? '&quot;' : '&apos;'
const fixQuoteChar = fixToDouble ? '"' : "'"

const replacement = fixQuoteChar + contentText.replace(quotePattern, quoteEscaped) + fixQuoteChar
return fixer.replaceText(node.value, replacement)
}
})
Expand Down
54 changes: 54 additions & 0 deletions tests/lib/rules/html-quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ tester.run('html-quotes', rule, {
code: "<template><div :class='foo'></div></template>",
options: ['single']
},
// avoidEscape
{
filename: 'test.vue',
code: "<template><div attr='foo\"bar'></div></template>",
options: ['double', { avoidEscape: true }]
},
{
filename: 'test.vue',
code: "<template><div attr=\"foo'bar\"></div></template>",
options: ['single', { avoidEscape: true }]
},

// Invalid EOF
{
Expand Down Expand Up @@ -166,6 +177,49 @@ tester.run('html-quotes', rule, {
output: "<template><div :class='foo+&apos;bar&apos;'></div></template>",
options: ['single'],
errors: ['Expected to be enclosed by single quotes.']
},
// avoidEscape
{
filename: 'test.vue',
code: "<template><div attr='foo'></div></template>",
output: '<template><div attr="foo"></div></template>',
options: ['double', { avoidEscape: true }],
errors: ['Expected to be enclosed by double quotes.']
},
{
filename: 'test.vue',
code: '<template><div attr="bar"></div></template>',
output: "<template><div attr='bar'></div></template>",
options: ['single', { avoidEscape: true }],
errors: ['Expected to be enclosed by single quotes.']
},
{
filename: 'test.vue',
code: '<template><div attr=foo"bar></div></template>',
output: '<template><div attr=\'foo"bar\'></div></template>',
options: ['double', { avoidEscape: true }],
errors: ['Expected to be enclosed by double quotes.']
},
{
filename: 'test.vue',
code: '<template><div attr=foo\'bar></div></template>',
output: "<template><div attr=\"foo'bar\"></div></template>",
options: ['single', { avoidEscape: true }],
errors: ['Expected to be enclosed by single quotes.']
},
{
filename: 'test.vue',
code: '<template><div attr=foo"bar\'baz></div></template>',
output: '<template><div attr="foo&quot;bar\'baz"></div></template>',
options: ['double', { avoidEscape: true }],
errors: ['Expected to be enclosed by double quotes.']
},
{
filename: 'test.vue',
code: '<template><div attr=foo"bar\'baz></div></template>',
output: '<template><div attr=\'foo"bar&apos;baz\'></div></template>',
options: ['single', { avoidEscape: true }],
errors: ['Expected to be enclosed by single quotes.']
}
]
})

0 comments on commit ca2c962

Please sign in to comment.