Skip to content

Commit 0464492

Browse files
Merge pull request #2566 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-quoteless-attributes
Extract rule: template-no-quoteless-attributes
2 parents 389c10b + 0b48e69 commit 0464492

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ rules in templates can be disabled with eslint directives with mustache or html
448448

449449
| Name | Description | 💼 | 🔧 | 💡 |
450450
| :------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :- | :- | :- |
451+
| [template-no-quoteless-attributes](docs/rules/template-no-quoteless-attributes.md) | require quotes on all attribute values | | 🔧 | |
451452
| [template-no-unnecessary-curly-strings](docs/rules/template-no-unnecessary-curly-strings.md) | disallow unnecessary curly braces in string interpolations | | 🔧 | |
452453

453454
### Stylistic Issues
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ember/template-no-quoteless-attributes
2+
3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
In HTML, all attribute values are considered strings, regardless of whether they are quoted or not.
8+
9+
The following two examples are _identical_ from the perspective of the browser:
10+
11+
<!-- prettier-ignore -->
12+
```html
13+
<div data-foo=asdf></div>
14+
<div data-foo="asdf"></div>
15+
```
16+
17+
This fact makes the following HTML very confusing:
18+
19+
<!-- prettier-ignore -->
20+
```html
21+
<input disabled=false>
22+
```
23+
24+
In this case, the simple _presence_ of the `disabled` attribute means that the `<input>` is disabled and setting the value to `false` doesn't do the obvious thing.
25+
26+
This is just _one_ (of many) cases where the default "string" based parsing of attributes in HTML can trip folks up.
27+
28+
This rule attempts to make this situation _slightly_ better by at least ensuring that all attribute values are quoted. This obviously doesn't fix the :troll:y nature of HTML here but it does ensure that you still **see** the quotes (which should hopefully help remind you that these are strings and not values).
29+
30+
## Examples
31+
32+
This rule **forbids** the following (note that `someValue` could have been intended either as a string or expression):
33+
34+
<!-- prettier-ignore -->
35+
```html
36+
<div data-foo=someValue></div>
37+
```
38+
39+
This rule **allows** the following:
40+
41+
```html
42+
<div data-foo="someValue"></div>
43+
```
44+
45+
```hbs
46+
<div data-foo={{someValue}}></div>
47+
```
48+
49+
## References
50+
51+
- [HTML spec/attributes](https://html.spec.whatwg.org/multipage/dom.html#attributes)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'require quotes on all attribute values',
7+
category: 'Style',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-quoteless-attributes.md',
9+
templateMode: 'both',
10+
},
11+
fixable: 'code',
12+
schema: [],
13+
messages: {
14+
missing: '{{type}} {{name}} should be either quoted or wrapped in mustaches',
15+
},
16+
originallyFrom: {
17+
name: 'ember-template-lint',
18+
rule: 'lib/rules/no-quoteless-attributes.js',
19+
docs: 'docs/rule/no-quoteless-attributes.md',
20+
tests: 'test/unit/rules/no-quoteless-attributes-test.js',
21+
},
22+
},
23+
create(context) {
24+
return {
25+
GlimmerAttrNode(node) {
26+
// Check if attribute has text value without quotes
27+
if (node.value?.type === 'GlimmerTextNode' && !/^["']/.test(node.value.chars)) {
28+
const sourceCode = context.sourceCode;
29+
const attrText = sourceCode.getText(node);
30+
31+
// If value looks unquoted (no = or =value without quotes)
32+
if (/=\s*[^"'{]/.test(attrText)) {
33+
const type = node.name?.startsWith('@') ? 'Argument' : 'Attribute';
34+
context.report({
35+
node,
36+
messageId: 'missing',
37+
data: { type, name: node.name },
38+
fix(fixer) {
39+
const valueText = node.value.chars;
40+
const replacementText = `${node.name}="${valueText}"`;
41+
return fixer.replaceText(node, replacementText);
42+
},
43+
});
44+
}
45+
}
46+
},
47+
};
48+
},
49+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const rule = require('../../../lib/rules/template-no-quoteless-attributes');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
ruleTester.run('template-no-quoteless-attributes', rule, {
9+
valid: [
10+
'<template><div class="foo"></div></template>',
11+
'<template><div data-foo="derp"></div></template>',
12+
'<template><div data-foo="derp {{stuff}}"></div></template>',
13+
'<template><div data-foo={{someValue}}></div></template>',
14+
'<template><div data-foo={{true}}></div></template>',
15+
'<template><div data-foo={{false}}></div></template>',
16+
'<template><div data-foo={{5}}></div></template>',
17+
'<template><SomeThing ...attributes /></template>',
18+
'<template><div></div></template>',
19+
'<template><input disabled></template>',
20+
],
21+
invalid: [
22+
{
23+
code: '<template><div class=foo></div></template>',
24+
output: '<template><div class="foo"></div></template>',
25+
errors: [{ messageId: 'missing' }],
26+
},
27+
28+
{
29+
code: '<template><div data-foo=asdf></div></template>',
30+
output: '<template><div data-foo="asdf"></div></template>',
31+
errors: [{ messageId: 'missing' }],
32+
},
33+
{
34+
code: '<template><SomeThing @blah=asdf /></template>',
35+
output: '<template><SomeThing @blah="asdf" /></template>',
36+
errors: [{ messageId: 'missing' }],
37+
},
38+
],
39+
});
40+
41+
const hbsRuleTester = new RuleTester({
42+
parser: require.resolve('ember-eslint-parser/hbs'),
43+
parserOptions: {
44+
ecmaVersion: 2022,
45+
sourceType: 'module',
46+
},
47+
});
48+
49+
hbsRuleTester.run('template-no-quoteless-attributes', rule, {
50+
valid: [
51+
'<div data-foo="derp"></div>',
52+
'<div data-foo="derp {{stuff}}"></div>',
53+
'<div data-foo={{someValue}}></div>',
54+
'<div data-foo={{true}}></div>',
55+
'<div data-foo={{false}}></div>',
56+
'<div data-foo={{5}}></div>',
57+
'<SomeThing ...attributes />',
58+
'<div></div>',
59+
'<input disabled>',
60+
],
61+
invalid: [
62+
{
63+
code: '<div data-foo=asdf></div>',
64+
output: '<div data-foo="asdf"></div>',
65+
errors: [{ message: 'Attribute data-foo should be either quoted or wrapped in mustaches' }],
66+
},
67+
{
68+
code: '<SomeThing @blah=asdf />',
69+
output: '<SomeThing @blah="asdf" />',
70+
errors: [{ message: 'Argument @blah should be either quoted or wrapped in mustaches' }],
71+
},
72+
],
73+
});

0 commit comments

Comments
 (0)