diff --git a/plugins/postcss-text-decoration-shorthand/CHANGELOG.md b/plugins/postcss-text-decoration-shorthand/CHANGELOG.md index b372012b9..2211bf56f 100644 --- a/plugins/postcss-text-decoration-shorthand/CHANGELOG.md +++ b/plugins/postcss-text-decoration-shorthand/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Text Decoration Shorthand +### Unreleased (patch) + +- Reduce redundant fallbacks for both `text-decoration` and `-webkit-text-decoration` + ### 5.0.1 _January 25, 2026_ diff --git a/plugins/postcss-text-decoration-shorthand/dist/index.mjs b/plugins/postcss-text-decoration-shorthand/dist/index.mjs index 46296ce26..86899a876 100644 --- a/plugins/postcss-text-decoration-shorthand/dist/index.mjs +++ b/plugins/postcss-text-decoration-shorthand/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";import{namedColors as o}from"@csstools/color-helpers";const t=/^text-decoration$/i,creator=o=>{const c=Object.assign({preserve:!0},o);return{postcssPlugin:"postcss-text-decoration-shorthand",prepare(){const o=new Map;return{postcssPlugin:"postcss-text-decoration-shorthand",OnceExit(){o.clear()},Declaration(i){if(!t.test(i.prop))return;const a=i.parent;if(!a)return;const u=a.index(i);if(a.nodes.some(e=>"decl"===e.type&&t.test(e.prop)&&o.get(i.value)===e.value&&a.index(e)!==u))return;const d=e(i.value),p=d.nodes.filter(e=>"space"!==e.type&&"comment"!==e.type);if(p.find(e=>"var"===e.value.toLowerCase()&&"function"===e.type))return;if(p.find(e=>"word"===e.type&&r.includes(e.value)))return;const f={line:[],style:null,color:null,thickness:null};for(let o=0;o{const c=Object.assign({preserve:!0},t);return{postcssPlugin:"postcss-text-decoration-shorthand",prepare(){const t=new Map;return{postcssPlugin:"postcss-text-decoration-shorthand",OnceExit(){t.clear()},Declaration(i){if(!o.test(i.prop))return;const a=i.parent;if(!a)return;const u=a.index(i),d=a.nodes.filter(e=>"decl"===e.type&&o.test(e.prop)&&a.index(e)!==u);if(d.some(e=>t.get(i.value)===e.value))return;const p=e(i.value),f=p.nodes.filter(e=>"space"!==e.type&&"comment"!==e.type);if(f.find(e=>"var"===e.value.toLowerCase()&&"function"===e.type))return;if(f.find(e=>"word"===e.type&&r.includes(e.value)))return;const v={line:[],style:null,color:null,thickness:null};for(let t=0;tt.get(i.value)===e.value))return;if(i.value.toLowerCase()===y.toLowerCase()){let e=i.next();for(;e&&"comment"===e.type;)e=e.next();return void(e&&"decl"===e.type&&"text-decoration"===e.prop.toLowerCase()||i.cloneBefore({prop:"-webkit-text-decoration",value:y}))}i.cloneBefore({prop:"text-decoration",value:y});const h=e.stringify([...v.line,{before:"",after:"",sourceIndex:0,sourceEndIndex:0,type:"space",value:" "},v.style,{before:"",after:"",sourceIndex:0,sourceEndIndex:0,type:"space",value:" "},v.color]);v.thickness&&i.cloneBefore({prop:"text-decoration",value:h}),v.thickness&&i.cloneBefore({prop:"text-decoration-thickness",value:e.stringify([v.thickness])}),t.set(h,y),c.preserve||i.remove()}}}}};function nodeIsAColor(e){return!("word"!==e.type||!e.value.startsWith("#"))||(!("word"!==e.type||!i.includes(e.value.toLowerCase()))||!("function"!==e.type||!c.includes(e.value.toLowerCase())))}creator.postcss=!0;const r=["unset","inherit","initial","revert","revert-layer"],n=["underline","overline","line-through","blink","spelling-error","grammar-error"],s=["solid","double","dotted","dashed","wavy"],l=["auto","from-font"],c=["color","color-mix","hsl","hsla","hwb","lab","lch","oklab","oklch","rgb","rgba"],i=["currentcolor","transparent",...Object.keys(t)];function genericNodeParts(){return{before:"",after:"",sourceIndex:0,sourceEndIndex:0}}export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-text-decoration-shorthand/src/index.ts b/plugins/postcss-text-decoration-shorthand/src/index.ts index 1246f170b..d5170b468 100644 --- a/plugins/postcss-text-decoration-shorthand/src/index.ts +++ b/plugins/postcss-text-decoration-shorthand/src/index.ts @@ -1,4 +1,4 @@ -import type { Plugin, PluginCreator } from 'postcss'; +import type { Declaration, Plugin, PluginCreator } from 'postcss'; import valueParser from 'postcss-value-parser'; import { namedColors } from '@csstools/color-helpers'; @@ -41,13 +41,16 @@ const creator: PluginCreator = (opts?: pluginOptions) => { } const ownIndex = parent.index(decl); - const hasFallbacksOrOverrides = parent.nodes.some((node) => { + + const siblingTextDecorationProperties = parent.nodes.filter((node) => { return node.type === 'decl' && IS_TEXT_DECORATION_REGEX.test(node.prop) && - convertedValues.get(decl.value) === node.value && parent.index(node) !== ownIndex; - }); - if (hasFallbacksOrOverrides) { + }) as Array; + + if (siblingTextDecorationProperties.some((node) => { + return convertedValues.get(decl.value) === node.value + })) { return; } @@ -191,8 +194,20 @@ const creator: PluginCreator = (opts?: pluginOptions) => { } const nonShortHandValue = valueParser.stringify(data.line); + convertedValues.set(decl.value, nonShortHandValue); + + if (siblingTextDecorationProperties.some((node) => { + return convertedValues.get(decl.value) === node.value + })) { + return; + } + if (decl.value.toLowerCase() === nonShortHandValue.toLowerCase()) { - const next = decl.next(); + let next = decl.next(); + while (next && next.type === 'comment') { + next = next.next(); + } + if (!next || next.type !== 'decl' || next.prop.toLowerCase() !== 'text-decoration') { // "-webkit-text-decoration" is a shorthand and sets omitted constituent properties to their initial value. @@ -243,7 +258,6 @@ const creator: PluginCreator = (opts?: pluginOptions) => { }); } - convertedValues.set(decl.value, nonShortHandValue); convertedValues.set(shortHandValue, nonShortHandValue); if (!options.preserve) { diff --git a/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.expect.css b/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.expect.css index 3166dc0e0..d05d6a084 100644 --- a/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.expect.css +++ b/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.expect.css @@ -185,3 +185,45 @@ -webkit-text-decoration: underline red overline; text-decoration: underline red overline; } + +.manual-fallback-a { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.manual-fallback-b { + text-decoration: overline; + -webkit-text-decoration: overline solid purple; + text-decoration: overline solid purple; + -webkit-text-decoration: overline purple 4px; + text-decoration: overline purple 4px; +} + +.manual-fallback-c { + text-decoration: overline; + -webkit-text-decoration: overline solid purple; + text-decoration: overline solid purple; + -webkit-text-decoration: overline purple 4px; + text-decoration: overline purple 4px; +} + +.fallback-without-comment { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.fallback-with-comment-a { + text-decoration: underline; /* 2 */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.fallback-with-comment-b { + text-decoration: underline; + -webkit-text-decoration: underline /* 2 */; + text-decoration: underline /* 2 */; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} diff --git a/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.preserve-false.expect.css b/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.preserve-false.expect.css index ec077b6c3..4b9f1e827 100644 --- a/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.preserve-false.expect.css +++ b/plugins/postcss-text-decoration-shorthand/test/basic.autoprefixer.preserve-false.expect.css @@ -152,3 +152,41 @@ -webkit-text-decoration: underline red overline; text-decoration: underline red overline; } + +.manual-fallback-a { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.manual-fallback-b { + text-decoration: overline; + -webkit-text-decoration: overline purple 4px; + text-decoration: overline purple 4px; +} + +.manual-fallback-c { + text-decoration: overline; + -webkit-text-decoration: overline solid purple; + text-decoration: overline solid purple; + -webkit-text-decoration: overline purple 4px; + text-decoration: overline purple 4px; +} + +.fallback-without-comment { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.fallback-with-comment-a { + text-decoration: underline; /* 2 */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +.fallback-with-comment-b { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} diff --git a/plugins/postcss-text-decoration-shorthand/test/basic.css b/plugins/postcss-text-decoration-shorthand/test/basic.css index 524d330b6..74784e50e 100644 --- a/plugins/postcss-text-decoration-shorthand/test/basic.css +++ b/plugins/postcss-text-decoration-shorthand/test/basic.css @@ -97,3 +97,34 @@ .ignored--d { text-decoration: underline red overline; } + +.manual-fallback-a { + text-decoration: underline; + text-decoration: underline dotted; +} + +.manual-fallback-b { + text-decoration: overline solid purple; + text-decoration: overline purple 4px; +} + +.manual-fallback-c { + text-decoration: overline; + text-decoration: overline solid purple; + text-decoration: overline purple 4px; +} + +.fallback-without-comment { + text-decoration: underline; + text-decoration: underline dotted; +} + +.fallback-with-comment-a { + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; +} + +.fallback-with-comment-b { + text-decoration: underline /* 2 */; + text-decoration: underline dotted; +} diff --git a/plugins/postcss-text-decoration-shorthand/test/basic.expect.css b/plugins/postcss-text-decoration-shorthand/test/basic.expect.css index 9e979f757..11ed5df4a 100644 --- a/plugins/postcss-text-decoration-shorthand/test/basic.expect.css +++ b/plugins/postcss-text-decoration-shorthand/test/basic.expect.css @@ -148,3 +148,36 @@ .ignored--d { text-decoration: underline red overline; } + +.manual-fallback-a { + text-decoration: underline; + text-decoration: underline dotted; +} + +.manual-fallback-b { + text-decoration: overline; + text-decoration: overline solid purple; + text-decoration: overline purple 4px; +} + +.manual-fallback-c { + text-decoration: overline; + text-decoration: overline solid purple; + text-decoration: overline purple 4px; +} + +.fallback-without-comment { + text-decoration: underline; + text-decoration: underline dotted; +} + +.fallback-with-comment-a { + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; +} + +.fallback-with-comment-b { + text-decoration: underline; + text-decoration: underline /* 2 */; + text-decoration: underline dotted; +} diff --git a/plugins/postcss-text-decoration-shorthand/test/basic.preserve-false.expect.css b/plugins/postcss-text-decoration-shorthand/test/basic.preserve-false.expect.css index 658b40ab5..7116b8498 100644 --- a/plugins/postcss-text-decoration-shorthand/test/basic.preserve-false.expect.css +++ b/plugins/postcss-text-decoration-shorthand/test/basic.preserve-false.expect.css @@ -133,3 +133,34 @@ .ignored--d { text-decoration: underline red overline; } + +.manual-fallback-a { + text-decoration: underline; + text-decoration: underline dotted; +} + +.manual-fallback-b { + text-decoration: overline; + text-decoration: overline purple 4px; +} + +.manual-fallback-c { + text-decoration: overline; + text-decoration: overline solid purple; + text-decoration: overline purple 4px; +} + +.fallback-without-comment { + text-decoration: underline; + text-decoration: underline dotted; +} + +.fallback-with-comment-a { + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; +} + +.fallback-with-comment-b { + text-decoration: underline; + text-decoration: underline dotted; +}