Skip to content

Commit

Permalink
postcss-content-alt-text: improve support for empty string alt text (
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmenke authored Jul 13, 2024
1 parent 26341df commit d233755
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 37 deletions.
4 changes: 4 additions & 0 deletions plugins/postcss-content-alt-text/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to PostCSS Content Alt Text

### Unreleased (patch)

- Add specific handling of `content: ">" / "";` as this pattern is used in the same way as `<img alt="">`, i.e. to represent an item that does not need a text alternative.

### 1.0.0

_July 7, 2024_
Expand Down
26 changes: 26 additions & 0 deletions plugins/postcss-content-alt-text/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@ npm install @csstools/postcss-content-alt-text --save-dev
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}
.bar {
content: ">" / "";
}
/* becomes */
.foo {
content: url(tree.jpg) "A beautiful tree in a dark forest";
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}
.bar {
content: ">" ;
content: ">" / "";
}
```

## Usage
Expand Down Expand Up @@ -67,11 +76,19 @@ postcssContentAltText({ preserve: false })
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}
.bar {
content: ">" / "";
}
/* becomes */
.foo {
content: url(tree.jpg) "A beautiful tree in a dark forest";
}
.bar {
content: ">" ;
}
```

### stripAltText
Expand All @@ -91,12 +108,21 @@ postcssContentAltText({ stripAltText: true })
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}
.bar {
content: ">" / "";
}
/* becomes */
.foo {
content: url(tree.jpg) ;
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}
.bar {
content: ">" ;
content: ">" / "";
}
```

[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
Expand Down
2 changes: 1 addition & 1 deletion plugins/postcss-content-alt-text/dist/index.cjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"use strict";var e=require("@csstools/css-parser-algorithms"),s=require("@csstools/css-tokenizer"),t=require("@csstools/postcss-progressive-custom-properties"),o=require("@csstools/utilities");const r={test:e=>e.includes("content:")&&e.includes("/")},basePlugin=t=>({postcssPlugin:"postcss-content-alt-text",Declaration(n){if("content"!==n.prop||!n.value.includes("/"))return;if(o.hasFallback(n))return;if(o.hasSupportsAtRuleAncestor(n,r))return;const i=e.parseListOfComponentValues(s.tokenize({css:n.value}));let c=0;for(let o=i.length-1;o>=0;o--){const r=i[o];if(!e.isTokenNode(r))continue;const n=r.value;s.isTokenDelim(n)&&("/"===n[4].value&&(c++,!0===t?.stripAltText?i.splice(o,i.length):i.splice(o,1)))}if(1!==c)return;const l=e.stringify([i]);l!==n.value&&(n.cloneBefore({value:l}),!1===t?.preserve&&n.remove())}});basePlugin.postcss=!0;const creator=e=>{const s=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0,stripAltText:!1},e);return s.enableProgressiveCustomProperties&&s.preserve?{postcssPlugin:"postcss-content-alt-text",plugins:[t(),basePlugin(s)]}:basePlugin(s)};creator.postcss=!0,module.exports=creator;
"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),s=require("@csstools/utilities"),t=require("@csstools/css-parser-algorithms"),r=require("@csstools/css-tokenizer");function transform(e,s){const o=e[0];if(!o.length)return"";if(s)return t.stringify([o]);const n=e[1].filter((e=>!t.isWhiteSpaceOrCommentNode(e)));return 1===n.length&&t.isTokenNode(n[0])&&r.isTokenString(n[0].value)&&""===n[0].value[4].value?t.stringify([o]):t.stringify([[...o,...e[1]]])}function parse(e){const s=t.parseListOfComponentValues(r.tokenize({css:e})),o=[];let n=0;for(let e=s.length-1;e>=0;e--){const i=s[e];if(!t.isTokenNode(i))continue;const l=i.value;r.isTokenDelim(l)&&("/"===l[4].value&&(o.push(s.slice(n,e)),n=e+1))}return 0!==n&&o.push(s.slice(n,s.length)),o}const o={test:e=>e.includes("content:")&&e.includes("/")},basePlugin=e=>({postcssPlugin:"postcss-content-alt-text",Declaration(t){if("content"!==t.prop||!t.value.includes("/"))return;if(s.hasFallback(t))return;if(s.hasSupportsAtRuleAncestor(t,o))return;const r=parse(t.value);if(2!==r.length)return;const n=transform(r,e?.stripAltText);n!==t.value&&(t.cloneBefore({value:n}),!1===e?.preserve&&t.remove())}});basePlugin.postcss=!0;const creator=s=>{const t=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0,stripAltText:!1},s);return t.enableProgressiveCustomProperties&&t.preserve?{postcssPlugin:"postcss-content-alt-text",plugins:[e(),basePlugin(t)]}:basePlugin(t)};creator.postcss=!0,module.exports=creator;
2 changes: 1 addition & 1 deletion plugins/postcss-content-alt-text/dist/index.mjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import{parseListOfComponentValues as s,isTokenNode as e,stringify as t}from"@csstools/css-parser-algorithms";import{tokenize as o,isTokenDelim as r}from"@csstools/css-tokenizer";import n from"@csstools/postcss-progressive-custom-properties";import{hasFallback as c,hasSupportsAtRuleAncestor as i}from"@csstools/utilities";const l={test:s=>s.includes("content:")&&s.includes("/")},basePlugin=n=>({postcssPlugin:"postcss-content-alt-text",Declaration(p){if("content"!==p.prop||!p.value.includes("/"))return;if(c(p))return;if(i(p,l))return;const u=s(o({css:p.value}));let a=0;for(let s=u.length-1;s>=0;s--){const t=u[s];if(!e(t))continue;const o=t.value;r(o)&&("/"===o[4].value&&(a++,!0===n?.stripAltText?u.splice(s,u.length):u.splice(s,1)))}if(1!==a)return;const m=t([u]);m!==p.value&&(p.cloneBefore({value:m}),!1===n?.preserve&&p.remove())}});basePlugin.postcss=!0;const creator=s=>{const e=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0,stripAltText:!1},s);return e.enableProgressiveCustomProperties&&e.preserve?{postcssPlugin:"postcss-content-alt-text",plugins:[n(),basePlugin(e)]}:basePlugin(e)};creator.postcss=!0;export{creator as default};
import s from"@csstools/postcss-progressive-custom-properties";import{hasFallback as t,hasSupportsAtRuleAncestor as e}from"@csstools/utilities";import{stringify as r,isWhiteSpaceOrCommentNode as o,isTokenNode as n,parseListOfComponentValues as c}from"@csstools/css-parser-algorithms";import{isTokenString as l,tokenize as i,isTokenDelim as u}from"@csstools/css-tokenizer";function transform(s,t){const e=s[0];if(!e.length)return"";if(t)return r([e]);const c=s[1].filter((s=>!o(s)));return 1===c.length&&n(c[0])&&l(c[0].value)&&""===c[0].value[4].value?r([e]):r([[...e,...s[1]]])}function parse(s){const t=c(i({css:s})),e=[];let r=0;for(let s=t.length-1;s>=0;s--){const o=t[s];if(!n(o))continue;const c=o.value;u(c)&&("/"===c[4].value&&(e.push(t.slice(r,s)),r=s+1))}return 0!==r&&e.push(t.slice(r,t.length)),e}const p={test:s=>s.includes("content:")&&s.includes("/")},basePlugin=s=>({postcssPlugin:"postcss-content-alt-text",Declaration(r){if("content"!==r.prop||!r.value.includes("/"))return;if(t(r))return;if(e(r,p))return;const o=parse(r.value);if(2!==o.length)return;const n=transform(o,s?.stripAltText);n!==r.value&&(r.cloneBefore({value:n}),!1===s?.preserve&&r.remove())}});basePlugin.postcss=!0;const creator=t=>{const e=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0,stripAltText:!1},t);return e.enableProgressiveCustomProperties&&e.preserve?{postcssPlugin:"postcss-content-alt-text",plugins:[s(),basePlugin(e)]}:basePlugin(e)};creator.postcss=!0;export{creator as default};
40 changes: 5 additions & 35 deletions plugins/postcss-content-alt-text/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { isTokenNode, parseListOfComponentValues, stringify } from '@csstools/css-parser-algorithms';
import { isTokenDelim, tokenize } from '@csstools/css-tokenizer';
import postcssProgressiveCustomProperties from '@csstools/postcss-progressive-custom-properties';
import { hasFallback, hasSupportsAtRuleAncestor } from '@csstools/utilities';
import type { PluginCreator } from 'postcss';
import { transform } from './transform';
import { parse } from './parse';

/** postcss-content-alt-text plugin options */
export type basePluginOptions = {
Expand Down Expand Up @@ -34,42 +34,12 @@ const basePlugin: PluginCreator<basePluginOptions> = (opts?: basePluginOptions)
return;
}

const componentValues = parseListOfComponentValues(
tokenize({ css: decl.value })
);

let slashCounter = 0;

for (let i = (componentValues.length - 1); i >= 0; i--) {
const componentValue = componentValues[i];
if (!isTokenNode(componentValue)) {
continue;
}

const token = componentValue.value;
if (!isTokenDelim(token)) {
continue;
}

if (token[4].value !== '/') {
continue;
}

slashCounter++;

if (opts?.stripAltText === true) {
componentValues.splice(i, componentValues.length);
} else {
componentValues.splice(i, 1);
}
}

if (slashCounter !== 1) {
// Either too few or too many slashes
const parts = parse(decl.value);
if (parts.length !== 2) {
return;
}

const modified = stringify([componentValues]);
const modified = transform(parts, opts?.stripAltText);

if (modified === decl.value) {
return;
Expand Down
37 changes: 37 additions & 0 deletions plugins/postcss-content-alt-text/src/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { ComponentValue } from "@csstools/css-parser-algorithms";
import { isTokenNode, parseListOfComponentValues } from "@csstools/css-parser-algorithms";
import { isTokenDelim, tokenize } from "@csstools/css-tokenizer";

export function parse(str: string): Array<Array<ComponentValue>> {
const componentValues = parseListOfComponentValues(
tokenize({ css: str })
);

const parts: Array<Array<ComponentValue>> = []
let lastSliceIndex = 0;

for (let i = (componentValues.length - 1); i >= 0; i--) {
const componentValue = componentValues[i];
if (!isTokenNode(componentValue)) {
continue;
}

const token = componentValue.value;
if (!isTokenDelim(token)) {
continue;
}

if (token[4].value !== '/') {
continue;
}

parts.push(componentValues.slice(lastSliceIndex, i));
lastSliceIndex = i + 1;
}

if (lastSliceIndex !== 0) {
parts.push(componentValues.slice(lastSliceIndex, componentValues.length));
}

return parts;
}
29 changes: 29 additions & 0 deletions plugins/postcss-content-alt-text/src/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ComponentValue } from "@csstools/css-parser-algorithms";
import { isTokenNode, isWhiteSpaceOrCommentNode, stringify } from "@csstools/css-parser-algorithms";
import { isTokenString } from "@csstools/css-tokenizer";

export function transform(parts: Array<Array<ComponentValue>>, stripAltText?: boolean): string {
const firstPart = parts[0];
if (!firstPart.length) {
return '';
}

if (stripAltText) {
return stringify([firstPart]);
}

const relevantComponentValues = parts[1].filter((x) => !isWhiteSpaceOrCommentNode(x));
if (
relevantComponentValues.length === 1 &&
isTokenNode(relevantComponentValues[0]) &&
isTokenString(relevantComponentValues[0].value) &&
relevantComponentValues[0].value[4].value === ''
) {
return stringify([firstPart]);
}

return stringify([[
...firstPart,
...parts[1],
]]);
}
8 changes: 8 additions & 0 deletions plugins/postcss-content-alt-text/test/basic.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@
content: "9" / "0";
}
}

.ignore {
/*
* An empty string is often used for illustrative items that do not require alt text.
* Appending an empty string might be visually breaking without having any benefits at all.
*/
content: ">" / "";
}
9 changes: 9 additions & 0 deletions plugins/postcss-content-alt-text/test/basic.expect.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,12 @@
content: "9" / "0";
}
}

.ignore {
/*
* An empty string is often used for illustrative items that do not require alt text.
* Appending an empty string might be visually breaking without having any benefits at all.
*/
content: ">" ;
content: ">" / "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@
content: "9" / "0";
}
}

.ignore {
/*
* An empty string is often used for illustrative items that do not require alt text.
* Appending an empty string might be visually breaking without having any benefits at all.
*/
content: ">" ;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,12 @@
content: "9" / "0";
}
}

.ignore {
/*
* An empty string is often used for illustrative items that do not require alt text.
* Appending an empty string might be visually breaking without having any benefits at all.
*/
content: ">" ;
content: ">" / "";
}
4 changes: 4 additions & 0 deletions plugins/postcss-content-alt-text/test/examples/example.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.foo {
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}

.bar {
content: ">" / "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
content: url(tree.jpg) "A beautiful tree in a dark forest";
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}

.bar {
content: ">" ;
content: ">" / "";
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.foo {
content: url(tree.jpg) "A beautiful tree in a dark forest";
}

.bar {
content: ">" ;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
content: url(tree.jpg) ;
content: url(tree.jpg) / "A beautiful tree in a dark forest";
}

.bar {
content: ">" ;
content: ">" / "";
}

0 comments on commit d233755

Please sign in to comment.