diff --git a/.changeset/many-bikes-rescue.md b/.changeset/many-bikes-rescue.md new file mode 100644 index 0000000000..f95fb15428 --- /dev/null +++ b/.changeset/many-bikes-rescue.md @@ -0,0 +1,5 @@ +--- +"stylelint": patch +--- + +Fixed: `declaration-block-no-redundant-longhand-properties` autofix for `grid-template` diff --git a/lib/rules/declaration-block-no-redundant-longhand-properties/__tests__/index.js b/lib/rules/declaration-block-no-redundant-longhand-properties/__tests__/index.js index c8f42136d2..9814d3dad2 100644 --- a/lib/rules/declaration-block-no-redundant-longhand-properties/__tests__/index.js +++ b/lib/rules/declaration-block-no-redundant-longhand-properties/__tests__/index.js @@ -144,6 +144,11 @@ testRule({ fixed: 'a { inset: 0 0 0 0; }', message: messages.expected('inset'), }, + { + 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; }`, + message: messages.expected('grid-template'), + }, ], }); diff --git a/lib/rules/declaration-block-no-redundant-longhand-properties/index.js b/lib/rules/declaration-block-no-redundant-longhand-properties/index.js index 0c254f4163..7121315715 100644 --- a/lib/rules/declaration-block-no-redundant-longhand-properties/index.js +++ b/lib/rules/declaration-block-no-redundant-longhand-properties/index.js @@ -24,6 +24,57 @@ const meta = { /** @typedef {import('postcss').Declaration} Declaration */ +/** @type {Map) => (string | undefined)>} */ +const customResolvers = new Map([ + [ + 'grid-template', + (decls) => { + const areas = decls.get('grid-template-areas')?.value.trim(); + const columns = decls.get('grid-template-columns')?.value.trim(); + const rows = decls.get('grid-template-rows')?.value.trim(); + + if (!(areas && columns && rows)) return; + + const splitAreas = [...areas.matchAll(/"[^"]+"/g)].map((x) => x[0]); + const splitRows = rows.split(' '); + + if (splitAreas.length === 0 || splitRows.length === 0) return; + + if (splitAreas.length !== splitRows.length) return; + + const zipped = splitAreas.map((area, i) => `${area} ${splitRows[i]}`).join(' '); + + return `${zipped} / ${columns}`; + }, + ], +]); + +/** + * @param {string} prefixedShorthandProperty + * @param {string[]} prefixedShorthandData + * @param {Map} transformedDeclarationNodes + * @returns {string | undefined} + */ +const resolveShorthandValue = ( + prefixedShorthandProperty, + prefixedShorthandData, + transformedDeclarationNodes, +) => { + const resolver = customResolvers.get(prefixedShorthandProperty); + + if (resolver === undefined) { + // the "default" resolver: sort the longhand values in the order + // of their properties + const values = prefixedShorthandData + .map((p) => transformedDeclarationNodes.get(p)?.value.trim()) + .filter(Boolean); + + return values.length > 0 ? values.join(' ') : undefined; + } + + return resolver(transformedDeclarationNodes); +}; + /** @type {import('stylelint').Rule} */ const rule = (primary, secondaryOptions, context) => { return (root, result) => { @@ -113,21 +164,24 @@ const rule = (primary, secondaryOptions, context) => { const transformedDeclarationNodes = new Map( declNodes.map((d) => [d.prop.toLowerCase(), d]), ); - const resolvedShorthandValue = prefixedShorthandData - .map((p) => transformedDeclarationNodes.get(p)?.value.trim()) - .filter(Boolean) - .join(' '); + const resolvedShorthandValue = resolveShorthandValue( + prefixedShorthandProperty, + prefixedShorthandData, + transformedDeclarationNodes, + ); - const newShorthandDeclarationNode = firstDeclNode.clone({ - prop: prefixedShorthandProperty, - value: resolvedShorthandValue, - }); + if (resolvedShorthandValue) { + const newShorthandDeclarationNode = firstDeclNode.clone({ + prop: prefixedShorthandProperty, + value: resolvedShorthandValue, + }); - firstDeclNode.replaceWith(newShorthandDeclarationNode); + firstDeclNode.replaceWith(newShorthandDeclarationNode); - declNodes.forEach((node) => node.remove()); + declNodes.forEach((node) => node.remove()); - return; + return; + } } }