Skip to content

Commit

Permalink
Fix declaration-block-no-redundant-longhand-properties autofix for …
Browse files Browse the repository at this point in the history
…`transition` (#6815)

Closes #6812.

This one required some non-trivial CSS spec reading, hence the complicated-ish behaviour!


---------

Co-authored-by: Masafumi Koba <473530+ybiquitous@users.noreply.github.com>
  • Loading branch information
mattxwang and ybiquitous committed Apr 27, 2023
1 parent 9611930 commit c3a9c9d
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/neat-poems-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `declaration-block-no-redundant-longhand-properties` autofix for `transition`
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ testRule({
code: 'a { margin-top: 0; margin-right: 0; margin-bottom: unset; margin-left: 0; }',
description: 'contains basic keyword (unset)',
},
{
code: 'a { transition-delay: 500ms, 1s; transition-duration: 250ms,2s; transition-timing-function: ease-in-out; transition-property: inherit; }',
description: 'transition property contains basic keyword (inherit)',
},
],

reject: [
Expand Down Expand Up @@ -148,9 +152,47 @@ testRule({
},
{
code: 'a { grid-template-rows: var(--header-h) 1fr var(--footer-h); grid-template-columns: var(--toolbar-w) 1fr; grid-template-areas: "header header" "toolbar main" "footer footer"; }',
fixed: `a { grid-template: "header header" var(--header-h) "toolbar main" 1fr "footer footer" var(--footer-h) / var(--toolbar-w) 1fr; }`,
fixed:
'a { grid-template: "header header" var(--header-h) "toolbar main" 1fr "footer footer" var(--footer-h) / var(--toolbar-w) 1fr; }',
description: 'custom resolver for grid-template',
message: messages.expected('grid-template'),
},
{
code: 'a { transition-delay: 500ms, 1s; transition-duration: 250ms,2s; transition-timing-function: ease-in-out; transition-property: transform, visibility; }',
fixed: 'a { transition: transform 250ms ease-in-out 500ms, visibility 2s ease-in-out 1s; }',
description:
'custom resolver for transition - multiple properties, delays, durations, timing-functions',
message: messages.expected('transition'),
},
{
code: 'a { transition-delay: 500ms; transition-duration: 250ms; transition-timing-function: ease-in-out; transition-property: transform, visibility; }',
fixed:
'a { transition: transform 250ms ease-in-out 500ms, visibility 250ms ease-in-out 500ms; }',
description:
'custom resolver for transition - multiple properties, single delay/duration/timing-function',
message: messages.expected('transition'),
},
{
code: 'a { transition-delay: 500ms, 1s, 1.5s; transition-duration: 250ms, 0.5s; transition-timing-function: ease-in-out; transition-property: transform, visibility, padding; }',
fixed:
'a { transition: transform 250ms ease-in-out 500ms, visibility 0.5s ease-in-out 1s, padding 250ms ease-in-out 1.5s; }',
description:
'custom resolver for transition - multiple properties, multiple delays/duration/timing-functions with different periods',
message: messages.expected('transition'),
},
{
code: 'a { transition-delay: 500ms, 1s; transition-duration: 250ms,2s; transition-timing-function: ease-in-out; transition-property: none; }',
fixed: 'a { transition: none 250ms ease-in-out 500ms; }',
description:
'custom resolver for transition - transition property contains property-specific basic keyword (none), but list is of size 1',
message: messages.expected('transition'),
},
{
code: 'a { transition-delay: ; transition-duration: 1s; transition-timing-function: ease; transition-property: top; }',
unfixable: true,
description: 'custom resolver for transition - missing transition-delay',
message: messages.expected('transition'),
},
],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,43 @@ const customResolvers = new Map([
return `${zipped} / ${columns}`;
},
],
[
'transition',
(decls) => {
/** @type {(input: string | undefined) => string[]} */
const commaSeparated = (input = '') =>
input
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0);
const delays = commaSeparated(decls.get('transition-delay')?.value);
const durations = commaSeparated(decls.get('transition-duration')?.value);
const timingFunctions = commaSeparated(decls.get('transition-timing-function')?.value);
const properties = commaSeparated(decls.get('transition-property')?.value);

if (!(delays.length && durations.length && timingFunctions.length && properties.length)) {
return;
}

// transition-property is the canonical list of the number of properties;
// see spec: https://w3c.github.io/csswg-drafts/css-transitions/#transition-property-property
// if there are more transition-properties than duration/delay/timings,
// the other properties are computed cyclically -- ex with %
// see spec example #3: https://w3c.github.io/csswg-drafts/css-transitions/#example-d94cbd75
return properties
.map((property, i) => {
return [
property,
durations[i % durations.length],
timingFunctions[i % timingFunctions.length],
delays[i % delays.length],
]
.filter(isString)
.join(' ');
})
.join(', ');
},
],
]);

/**
Expand Down

0 comments on commit c3a9c9d

Please sign in to comment.