Skip to content

Commit

Permalink
Add autofix to selector-attribute-quotes (#5248)
Browse files Browse the repository at this point in the history
  • Loading branch information
doing-art authored Apr 23, 2021
1 parent 911b196 commit 2a35e1a
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 10 deletions.
2 changes: 2 additions & 0 deletions lib/rules/selector-attribute-quotes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Require or disallow quotes for attribute values.
* These quotes */
```

The [`fix` option](../../../docs/user-guide/usage/options.md#fix) can automatically fix most of the problems reported by this rule.

## Options

`string`: `"always"|"never"`
Expand Down
93 changes: 93 additions & 0 deletions lib/rules/selector-attribute-quotes/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ testRule({
ruleName,
config: ['always'],
skipBasicChecks: true,
fix: true,

accept: [
{
Expand Down Expand Up @@ -54,51 +55,87 @@ testRule({
code: 'html { --custom-property-set: {} }',
description: 'custom property set in selector',
},
{
code: `a[href="te's't"] { }`,
description: 'double-quoted attribute contains single quote',
},
{
code: `a[href='te"s"t'] { }`,
description: 'single-quoted attribute contains double quote',
},
],

reject: [
{
code: 'a[title=flower] { }',
fixed: 'a[title="flower"] { }',
message: messages.expected('flower'),
line: 1,
column: 9,
},
{
code: 'a[ title=flower ] { }',
fixed: 'a[ title="flower" ] { }',
message: messages.expected('flower'),
line: 1,
column: 10,
},
{
code: '[class^=top] { }',
fixed: '[class^="top"] { }',
message: messages.expected('top'),
line: 1,
column: 9,
},
{
code: '[class ^= top] { }',
fixed: '[class ^= "top"] { }',
message: messages.expected('top'),
line: 1,
column: 11,
},
{
code: '[frame=hsides i] { }',
fixed: '[frame="hsides" i] { }',
message: messages.expected('hsides'),
line: 1,
column: 8,
},
{
code: '[data-style=value][data-loading] { }',
fixed: '[data-style="value"][data-loading] { }',
message: messages.expected('value'),
line: 1,
column: 13,
},
{
code: `[href=te\\'s\\"t] { }`,
fixed: `[href="te's\\"t"] { }`,
message: messages.expected(`te's"t`),
line: 1,
column: 7,
},
{
code: '[href=\\"test\\"] { }',
fixed: '[href="\\"test\\""] { }',
message: messages.expected('"test"'),
line: 1,
column: 7,
},
{
code: "[href=\\'test\\'] { }",
fixed: `[href="'test'"] { }`,
message: messages.expected("'test'"),
line: 1,
column: 7,
},
],
});

testRule({
ruleName,
config: ['never'],
fix: true,

accept: [
{
Expand All @@ -116,63 +153,119 @@ testRule({
{
code: '[data-style=value][data-loading] { }',
},
{
code: `a[href=te\\'s\\"t] { }`,
description: 'attribute contains inner quotes',
},
{
code: '[href=\\"test\\"] { }',
description: 'escaped double-quotes are not considered as framing quotes',
},
{
code: "[href=\\'test\\'] { }",
description: 'escaped single-quotes are not considered as framing quotes',
},
],

reject: [
{
code: 'a[target="_blank"] { }',
fixed: 'a[target=_blank] { }',
message: messages.rejected('_blank'),
line: 1,
column: 10,
},
{
code: 'a[ target="_blank" ] { }',
fixed: 'a[ target=_blank ] { }',
message: messages.rejected('_blank'),
line: 1,
column: 11,
},
{
code: '[class|="top"] { }',
fixed: '[class|=top] { }',
message: messages.rejected('top'),
line: 1,
column: 9,
},
{
code: '[class |= "top"] { }',
fixed: '[class |= top] { }',
message: messages.rejected('top'),
line: 1,
column: 11,
},
{
code: "[title~='text'] { }",
fixed: '[title~=text] { }',
message: messages.rejected('text'),
line: 1,
column: 9,
},
{
code: "[data-attribute='component'] { }",
fixed: '[data-attribute=component] { }',
message: messages.rejected('component'),
line: 1,
column: 17,
},
{
code: '[frame="hsides" i] { }',
fixed: '[frame=hsides i] { }',
message: messages.rejected('hsides'),
line: 1,
column: 8,
},
{
code: "[frame='hsides' i] { }",
fixed: '[frame=hsides i] { }',
message: messages.rejected('hsides'),
line: 1,
column: 8,
},
{
code: "[data-style='value'][data-loading] { }",
fixed: '[data-style=value][data-loading] { }',
message: messages.rejected('value'),
line: 1,
column: 13,
},
{
code: `[href="te'st"] { }`,
fixed: "[href=te\\'st] { }",
message: messages.rejected("te'st"),
line: 1,
column: 7,
},
{
code: `[href='te"st'] { }`,
fixed: '[href=te\\"st] { }',
message: messages.rejected('te"st'),
line: 1,
column: 7,
},
{
code: "[href='te\\'s\\'t'] { }",
fixed: "[href=te\\'s\\'t] { }",
message: messages.rejected("te's't"),
line: 1,
column: 7,
},
{
code: '[href="te\\"s\\"t"] { }',
fixed: '[href=te\\"s\\"t] { }',
message: messages.rejected('te"s"t'),
line: 1,
column: 7,
},
{
code: 'a[target="_blank"], /* comment */ a { }',
fixed: 'a[target=_blank], /* comment */ a { }',
message: messages.rejected('_blank'),
line: 1,
column: 10,
},
],
});

Expand Down
39 changes: 29 additions & 10 deletions lib/rules/selector-attribute-quotes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

'use strict';

const getRuleSelector = require('../../utils/getRuleSelector');
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
const parseSelector = require('../../utils/parseSelector');
const report = require('../../utils/report');
Expand All @@ -15,7 +16,9 @@ const messages = ruleMessages(ruleName, {
rejected: (value) => `Unexpected quotes around "${value}"`,
});

function rule(expectation) {
const acceptedQuoteMark = '"';

function rule(expectation, secondary, context) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, {
actual: expectation,
Expand All @@ -35,26 +38,42 @@ function rule(expectation) {
return;
}

parseSelector(ruleNode.selector, result, ruleNode, (selectorTree) => {
parseSelector(getRuleSelector(ruleNode), result, ruleNode, (selectorTree) => {
let selectorFixed = false;

selectorTree.walkAttributes((attributeNode) => {
if (!attributeNode.operator) {
return;
}

if (!attributeNode.quoted && expectation === 'always') {
complain(
messages.expected(attributeNode.value),
attributeNode.sourceIndex + attributeNode.offsetOf('value'),
);
if (context.fix) {
selectorFixed = true;
attributeNode.quoteMark = acceptedQuoteMark;
} else {
complain(
messages.expected(attributeNode.value),
attributeNode.sourceIndex + attributeNode.offsetOf('value'),
);
}
}

if (attributeNode.quoted && expectation === 'never') {
complain(
messages.rejected(attributeNode.value),
attributeNode.sourceIndex + attributeNode.offsetOf('value'),
);
if (context.fix) {
selectorFixed = true;
attributeNode.quoteMark = null;
} else {
complain(
messages.rejected(attributeNode.value),
attributeNode.sourceIndex + attributeNode.offsetOf('value'),
);
}
}
});

if (selectorFixed) {
ruleNode.selector = selectorTree.toString();
}
});

function complain(message, index) {
Expand Down
13 changes: 13 additions & 0 deletions lib/utils/getRuleSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict';

const _ = require('lodash');

/**
* @param {import('postcss').Rule} ruleNode
* @returns {string}
*/
function getRuleSelector(ruleNode) {
return _.get(ruleNode, 'raws.selector.raw', ruleNode.selector);
}

module.exports = getRuleSelector;

0 comments on commit 2a35e1a

Please sign in to comment.