From 715e90342aae5873dac288329e81cb22fb554893 Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Fri, 16 Oct 2026 11:45:31 +0300 Subject: [PATCH 1/4] test(server): typecheck the postcss packages against postcss 8.5.23 The committed lockfile resolves postcss to 8.4.31, so the typings of 8.5 are never checked. The script swaps in the manifests and lockfile of the postcss 8.5.23 update, runs typecheck for @linaria/server, @linaria/postcss-linaria and @linaria/atomic, and puts everything back. It fails today: TS18048 for the unguarded AtRule nodes in packages/server/src/collect.ts and TS2322 for the raw and rawValue returns in packages/postcss-linaria/src/stringify.ts. --- scripts/typecheck-postcss-8-5.sh | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100755 scripts/typecheck-postcss-8-5.sh diff --git a/scripts/typecheck-postcss-8-5.sh b/scripts/typecheck-postcss-8-5.sh new file mode 100755 index 000000000..53c7e8962 --- /dev/null +++ b/scripts/typecheck-postcss-8-5.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# Typechecks the workspace packages that use postcss (@linaria/server, +# @linaria/postcss-linaria and @linaria/atomic) against postcss 8.5.23. +# +# The committed manifests and lockfile still resolve postcss to 8.4.31, so the +# script swaps in the ones of the dependabot update to 8.5.23, installs with +# --ignore-scripts, runs `typecheck` of the three packages (`tsc --noEmit +# --composite false`, after the declarations of their dependencies are built) +# and puts the committed manifests, lockfile and install back on exit. +# +# Exit code: 0 when the packages typecheck, non-zero otherwise. +set -eu + +# The head of the dependabot pull request that updates postcss to 8.5.23. +update=05b0d4ca86d3cd33a46425ad124c6308be3531e0 +files="pnpm-lock.yaml packages/atomic/package.json packages/postcss-linaria/package.json packages/server/package.json" + +cd "$(dirname "$0")/.." + +git cat-file -e "$update^{commit}" || { + echo "commit $update is not in this repository" >&2 + exit 2 +} + +# Plain output: the failure signature is matched against it. +export NO_COLOR=1 FORCE_COLOR=0 + +backup=$(mktemp -d) + +restore() { + status=$? + trap - EXIT + for file in $files; do + cp "$backup/$(echo "$file" | tr / _)" "$file" + done + rm -rf "$backup" + pnpm install --frozen-lockfile --ignore-scripts >/dev/null 2>&1 || true + exit "$status" +} +trap restore EXIT + +for file in $files; do + cp "$file" "$backup/$(echo "$file" | tr / _)" + git show "$update:$file" >"$file" +done + +pnpm install --frozen-lockfile --ignore-scripts + +pnpm --filter ./packages/atomic --filter ./packages/postcss-linaria --filter ./packages/server list postcss --depth 0 + +# --continue: report the errors of every package, not only of the first one +# that fails. +pnpm exec turbo run typecheck --force --continue \ + --filter=@linaria/atomic \ + --filter=@linaria/postcss-linaria \ + --filter=@linaria/server From 7de50ea3b14fc5475ee6727d053fb3434efcb1b9 Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Fri, 16 Oct 2026 11:57:57 +0300 Subject: [PATCH 2/4] fix(server): guard the optional at-rule nodes in collect postcss 8.5 types `AtRule#nodes` as possibly undefined, so the typecheck of @linaria/server fails against it. `handleAtRule` only receives the parents of rules, which always have nodes, so reading them through optional chaining keeps the output unchanged on postcss 8.4 and 8.5. --- packages/server/src/collect.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/src/collect.ts b/packages/server/src/collect.ts index ef24619f8..aabcb4e9b 100644 --- a/packages/server/src/collect.ts +++ b/packages/server/src/collect.ts @@ -93,7 +93,7 @@ export default function collect( let removedNodesFromOther = 0; criticalRule.each((childRule: ChildNode, index: number) => { if (isCritical(childRule)) { - otherRule.nodes[index - removedNodesFromOther]?.remove(); + otherRule.nodes?.[index - removedNodesFromOther]?.remove(); removedNodesFromOther += 1; } else { childRule.remove(); @@ -102,10 +102,10 @@ export default function collect( rule.remove(); - if (criticalRule.nodes.length > 0) { + if (criticalRule.nodes?.length) { critical.append(criticalRule); } - if (otherRule.nodes.length > 0) { + if (otherRule.nodes?.length) { other.append(otherRule); } }; From 189e752b07d930ffe4b96d4a2c41cd57d60a8f4b Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Fri, 16 Oct 2026 11:58:02 +0300 Subject: [PATCH 3/4] fix(postcss): keep the stringifier raw overrides typed as strings postcss 8.5 widens the declared results of `Stringifier#raw` to `boolean | string` and of `Stringifier#rawValue` to `number | string`, while 8.4 declares `string` for both. The overrides return the base result unchanged and keep their `string` signatures, which the raws and fields the stringifier reads through them satisfy, so they compile against both versions without a runtime change. --- packages/postcss-linaria/src/stringify.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/postcss-linaria/src/stringify.ts b/packages/postcss-linaria/src/stringify.ts index 215e027ca..591697751 100644 --- a/packages/postcss-linaria/src/stringify.ts +++ b/packages/postcss-linaria/src/stringify.ts @@ -384,7 +384,10 @@ class LinariaStringifier extends Stringifier { ) { return node.raws.linariaBetween; } - return super.raw(node, own, detect); + // postcss 8.5 widens this to `boolean | string` (the `semicolon` raw is a + // boolean); 8.4 declares `string`. The base result is returned unchanged, + // and the raws this class reads through `raw` are strings. + return super.raw(node, own, detect) as string; } /** @inheritdoc */ @@ -400,7 +403,10 @@ class LinariaStringifier extends Stringifier { return `${node.raws[linariaProp]}`; } - return super.rawValue(node, prop); + // postcss 8.5 widens this to `number | string`; 8.4 declares `string`. The + // base result is returned unchanged, and the fields this class reads + // through `rawValue` are strings. + return super.rawValue(node, prop) as string; } /** @inheritdoc */ From ba6962c1a2ebeebf780da11e37c41d5d507929c4 Mon Sep 17 00:00:00 2001 From: Anton Evzhakov Date: Fri, 16 Oct 2026 12:12:43 +0300 Subject: [PATCH 4/4] docs(postcss): correct the comments on the stringifier raw overrides `Stringifier#raw` does return a boolean at runtime: the inherited `body` reads the `semicolon` raw through it. The comments claimed that every raw read through the override is a string. Say instead that `string` is the declared signature the calls in this class rely on, and that the base result is returned unchanged. Comment-only, no code change. --- packages/postcss-linaria/src/stringify.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/postcss-linaria/src/stringify.ts b/packages/postcss-linaria/src/stringify.ts index 591697751..e0a5ba005 100644 --- a/packages/postcss-linaria/src/stringify.ts +++ b/packages/postcss-linaria/src/stringify.ts @@ -384,9 +384,10 @@ class LinariaStringifier extends Stringifier { ) { return node.raws.linariaBetween; } - // postcss 8.5 widens this to `boolean | string` (the `semicolon` raw is a - // boolean); 8.4 declares `string`. The base result is returned unchanged, - // and the raws this class reads through `raw` are strings. + // postcss 8.5 declares the result as `boolean | string`, 8.4 as `string`. + // At runtime it is a boolean for the `semicolon` raw that the inherited + // `body` reads, so `string` is only the signature that the calls in this + // class rely on. The base result is returned unchanged. return super.raw(node, own, detect) as string; } @@ -403,9 +404,9 @@ class LinariaStringifier extends Stringifier { return `${node.raws[linariaProp]}`; } - // postcss 8.5 widens this to `number | string`; 8.4 declares `string`. The - // base result is returned unchanged, and the fields this class reads - // through `rawValue` are strings. + // postcss 8.5 declares the result as `number | string`, 8.4 as `string`. + // The base result is returned unchanged; the fields read through + // `rawValue` (`params`, `value` and `selector`) are strings. return super.rawValue(node, prop) as string; }