Skip to content

Commit

Permalink
Add options for different html / hbs quote styles in quotes rule (
Browse files Browse the repository at this point in the history
#2754)

Co-authored-by: Lucy Lin <lucy.ly.lin@gmail.com>
  • Loading branch information
robclancy and lin-ll committed May 24, 2023
1 parent b2a1a3e commit d9ec9c4
Show file tree
Hide file tree
Showing 3 changed files with 238 additions and 24 deletions.
23 changes: 20 additions & 3 deletions docs/rule/quotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,29 @@ or:

The following values are valid configuration:

* string -- "double" requires the use of double quotes wherever possible, "single" requires the use of single quotes wherever possible
- string -- "double" requires the use of double quotes wherever possible, "single" requires the use of single quotes wherever possible
- object -- { curlies: "single"|"double"|false, html: "single"|"double"|false } - requires different quotes for Handlebars and HTML syntax

For the object config, the properties `curlies` and `html` can be passed one of the following values: "single", "double", or `false`. If `false` is passed to a property, it will be as if this rule is turned off for that specific syntax.

With the config `{ curlies: false, html: "double" }`, this would be **forbidden**:

```hbs
<div foo='bar'></div>
```

However, this would be **allowed**:

```hbs
{{component "foo"}}
{{test x='y'}}
<div foo="bar"></div>
```

## Related Rules

* [quotes](https://eslint.org/docs/rules/quotes) from eslint
- [quotes](https://eslint.org/docs/rules/quotes) from eslint

## References

* [Google style guide/quotes](https://google.github.io/styleguide/htmlcssguide.html#HTML_Quotation_Marks)
- [Google style guide/quotes](https://google.github.io/styleguide/htmlcssguide.html#HTML_Quotation_Marks)
84 changes: 63 additions & 21 deletions lib/rules/quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ export default class Quotes extends Rule {
}
case 'string': {
if (['double', 'single'].includes(config)) {
return {
curlies: config,
html: config,
};
}
break;
}
case 'object': {
if (
Object.keys(config).length === 2 &&
['double', 'single', false].includes(config.curlies) &&
['double', 'single', false].includes(config.html)
) {
if (!config.curlies && !config.html) {
return false;
}
return config;
}
break;
Expand All @@ -28,6 +44,7 @@ export default class Quotes extends Rule {
[
' * "double" - requires the use of double quotes wherever possible',
' * "single" - requires the use of single quotes wherever possible',
' * { curlies: "single"|"double"|false, html: "single"|"double"|false } - requires different quotes for Handlebars and HTML syntax',
],
config
);
Expand All @@ -36,36 +53,57 @@ export default class Quotes extends Rule {
}

visitor() {
let badChar;
let goodChar;
const chars = {
single: "'",
double: '"',
};
const goodChars = {
curlies: chars[this.config.curlies],
html: chars[this.config.html],
};
const badChars = {};
if (goodChars.curlies) {
badChars.curlies = goodChars.curlies === chars.single ? chars.double : chars.single;
}
if (goodChars.html) {
badChars.html = goodChars.html === chars.single ? chars.double : chars.single;
}

let message;
switch (this.config) {
case 'double': {
badChar = "'";
goodChar = '"';
message = 'you must use double quotes in templates';
break;
}
case 'single': {
badChar = '"';
goodChar = "'";
message = 'you must use single quotes in templates';
break;
}

if (goodChars.curlies === chars.single && goodChars.html === chars.single) {
message = 'you must use single quotes in templates';
} else if (goodChars.curlies === chars.double && goodChars.html === chars.double) {
message = 'you must use double quotes in templates';
} else if (!goodChars.curlies || !goodChars.html) {
const correctQuote =
goodChars.curlies === chars.single || goodChars.html === chars.single ? 'single' : 'double';
message = `you must use ${correctQuote} quotes in ${
goodChars.curlies ? 'Handlebars syntax' : 'HTML attributes'
}`;
} else {
const double = goodChars.curlies === chars.double ? 'Handlebars syntax' : 'HTML attributes';
const single = goodChars.curlies === chars.single ? 'Handlebars syntax' : 'HTML attributes';

message = `you must use double quotes for ${double} and single quotes for ${single} in templates`;
}

return {
AttrNode(node) {
if (!node.isValueless && node.quoteType === badChar) {
if (attrValueHasChar(node.value, goodChar)) {
if (!goodChars.html) {
return;
}

if (!node.isValueless && node.quoteType === badChars.html) {
if (attrValueHasChar(node.value, goodChars.html)) {
// TODO: Autofix blocked on: https://github.com/ember-template-lint/ember-template-recast/issues/698
return this.log({
message,
node,
});
}
if (this.mode === 'fix') {
node.quoteType = goodChar;
node.quoteType = goodChars.html;
} else {
return this.log({
message,
Expand All @@ -77,10 +115,14 @@ export default class Quotes extends Rule {
},

StringLiteral(node, path) {
if (!goodChars.curlies) {
return;
}

let errorSource = this.sourceForNode(path.parentNode);

if (node.quoteType === badChar) {
if (node.value.includes(goodChar)) {
if (node.quoteType === badChars.curlies) {
if (node.value.includes(goodChars.curlies)) {
// TODO: Autofix blocked on: https://github.com/ember-template-lint/ember-template-recast/issues/698
return this.log({
message,
Expand All @@ -89,7 +131,7 @@ export default class Quotes extends Rule {
});
}
if (this.mode === 'fix') {
node.quoteType = goodChar;
node.quoteType = goodChars.curlies;
} else {
return this.log({
message,
Expand Down
155 changes: 155 additions & 0 deletions test/unit/rules/quotes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ generateRuleTests({
name: 'quotes',

good: [
// string config
{
config: 'double',
template: '{{component "test"}}',
Expand All @@ -28,6 +29,28 @@ generateRuleTests({
config: 'single',
template: "<input type='checkbox'>",
},

// object config
{
config: { curlies: false, html: false },
template: `{{component "test"}} {{hello x='test'}} <input type='checkbox'> <input type="checkbox">`,
},
{
config: { curlies: false, html: 'single' },
template: `{{component "test"}} {{hello x='test'}} <input type='checkbox'>`,
},
{
config: { curlies: 'double', html: false },
template: `{{component "test"}} <input type='checkbox'> <input type="checkbox">`,
},
{
config: { curlies: 'single', html: 'double' },
template: `<input type="checkbox"> {{hello 'test' x='test'}}`,
},
{
config: { curlies: 'double', html: 'single' },
template: `<input type='checkbox'> {{hello "test" x="test"}}`,
},
],

bad: [
Expand Down Expand Up @@ -268,6 +291,102 @@ generateRuleTests({
`);
},
},
{
config: { curlies: 'double', html: 'single' },
template: `<input type="checkbox"> {{hello 'test' x='test'}}`,
fixedTemplate: `<input type='checkbox'> {{hello "test" x="test"}}`,

verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
[
{
"column": 7,
"endColumn": 22,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates",
"rule": "quotes",
"severity": 2,
"source": "type=\\"checkbox\\"",
},
{
"column": 32,
"endColumn": 38,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates",
"rule": "quotes",
"severity": 2,
"source": "{{hello 'test' x='test'}}",
},
{
"column": 41,
"endColumn": 47,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for Handlebars syntax and single quotes for HTML attributes in templates",
"rule": "quotes",
"severity": 2,
"source": "x='test'",
},
]
`);
},
},
{
config: { curlies: 'single', html: 'double' },
template: `<input type='checkbox'> {{hello "test" x="test"}}`,
fixedTemplate: `<input type="checkbox"> {{hello 'test' x='test'}}`,

verifyResults(results) {
expect(results).toMatchInlineSnapshot(`
[
{
"column": 7,
"endColumn": 22,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates",
"rule": "quotes",
"severity": 2,
"source": "type='checkbox'",
},
{
"column": 32,
"endColumn": 38,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates",
"rule": "quotes",
"severity": 2,
"source": "{{hello \\"test\\" x=\\"test\\"}}",
},
{
"column": 41,
"endColumn": 47,
"endLine": 1,
"filePath": "layout.hbs",
"isFixable": true,
"line": 1,
"message": "you must use double quotes for HTML attributes and single quotes for Handlebars syntax in templates",
"rule": "quotes",
"severity": 2,
"source": "x=\\"test\\"",
},
]
`);
},
},
],

error: [
Expand All @@ -289,5 +408,41 @@ generateRuleTests({
message: 'You specified `true`',
},
},
{
config: { curlies: 'double', html: 'sometimes' },
template: 'test',

result: {
fatal: true,
message: 'You specified `{"curlies":"double","html":"sometimes"}`',
},
},
{
config: { curlies: 'double' },
template: 'test',

result: {
fatal: true,
message: 'You specified `{"curlies":"double"}`',
},
},
{
config: { html: 'sometimes' },
template: 'test',

result: {
fatal: true,
message: 'You specified `{"html":"sometimes"}`',
},
},
{
config: { curlies: 'double', html: 'single', other: 'foobar' },
template: 'test',

result: {
fatal: true,
message: 'You specified `{"curlies":"double","html":"single","other":"foobar"}`',
},
},
],
});

0 comments on commit d9ec9c4

Please sign in to comment.