Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add avoidEscape option to vue/html-quotes rule #1031

Merged
merged 1 commit into from Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 24 additions & 1 deletion docs/rules/html-quotes.md
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
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
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.']
}
]
})