Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to CSS Dedup are documented in this file, which is (mostly)

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

* Removed dead and no-op code across the engine, CLI, and plugin (redundant guards, unread fields and arguments, identical-branch conditionals, unused exports)

## [1.13.3] - 2026-09-24

### Changed
Expand Down
2 changes: 1 addition & 1 deletion src/cli/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export function runPool(slots, settings, onOutcome) {

// Every worker starts before anything is dispatched, so a pool that can’t
// start at all has yet to write a file or print a line
const size = Math.min(poolSize(queue.length), queue.length);
const size = poolSize(queue.length);
try {
for (let i = 0; i < size; i++) {
const worker = new Worker(new URL(import.meta.url), {
Expand Down
2 changes: 0 additions & 2 deletions src/cli/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ function renderFixPass(payload, { isStdin, label, multi, flags, savingsOnly }) {
exitFailure: skipped.length > 0 || Boolean(withheld),
errored: false,
stats: {
findings: null,
applied,
skipped: skipped.length,
bytesBefore: bytes.before,
Expand Down Expand Up @@ -297,7 +296,6 @@ function printOverallFixSummary(ok, savingsOnly) {
filesGrowLen: filesGrow.length,
growTotal,
totalBefore: totalBeforeAll,
skipFlag: '--fix --savings-only',
});
if (outcome) {
for (const line of outcome) console.log(line);
Expand Down
6 changes: 2 additions & 4 deletions src/consolidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,8 @@ function consolidateRoot(root, options = {}) {
const initiallyEmpty = new Set();
const aggressive = options.aggressive ?? false;
const conditionBlocks = aggressive ? collectConditionBlocks(root) : [];
if (aggressive) {
for (const atrule of conditionBlocks) {
if (atrule.nodes && !atrule.nodes.length) initiallyEmpty.add(atrule);
}
for (const atrule of conditionBlocks) {
if (atrule.nodes && !atrule.nodes.length) initiallyEmpty.add(atrule);
}

// Bringing the style sheet to the state it would ship in. Idempotent, so the
Expand Down
2 changes: 1 addition & 1 deletion src/lib/hacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// into a shared selector list is risky: Browsers that don’t recognize it can
// drop the entire rule, not just that one selector. Legacy hacks that rely on
// a selector being invalid/valid in specific browsers have the same problem.
export const DEFAULT_IGNORE_PATTERNS = [
const DEFAULT_IGNORE_PATTERNS = [
/::?-(?:moz|webkit|ms|o)-/i, // vendor-prefixed pseudo-classes/elements
/\*\s*html/i, // IE6 star-html hack
/\*\s*\+\s*html/i, // IE7 star-plus-html hack
Expand Down
12 changes: 6 additions & 6 deletions src/lib/normalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ const ZERO_PERCENT_SENSITIVE_PROPS = new Set([

// Collapses a decimal number’s redundant leading/trailing zeros, so `0.5`,
// `.5`, and `0.50` compare equal, as do `1.0` and `1`
const RE_DECIMAL = /(-?)(\d*)\.(\d+)/g;
const RE_DECIMAL = /(\d*)\.(\d+)/g;

function normalizeDecimals(value) {
return value.replace(RE_DECIMAL, (_match, sign, intPart, fracPart) => {
return value.replace(RE_DECIMAL, (_match, intPart, fracPart) => {
const trimmedFrac = fracPart.replace(/0+$/, '');
const int = intPart === '' ? '0' : intPart;
if (trimmedFrac === '') return `${sign}${int}`;
return `${sign}${int === '0' ? '' : int}.${trimmedFrac}`;
if (trimmedFrac === '') return int;
return `${int === '0' ? '' : int}.${trimmedFrac}`;
});
}

Expand Down Expand Up @@ -292,7 +292,7 @@ const PROPERTY_ALIASES = {
'grid-column-gap': 'column-gap',
};

// Per run (see `src/caches.js`). One cache per mode rather than one keyed by
// Per run (see `caches.js`). One cache per mode rather than one keyed by
// mode plus name: building that composite key would allocate per lookup.
const propCacheDefault = new Map();
const propCacheAggressive = new Map();
Expand Down Expand Up @@ -375,7 +375,7 @@ export function normalizeValue(prop, rawValue, aggressive = false) {

value = value.replace(RE_OPAQUE_PLACEHOLDER, (_match, index) => opaques[index]);

if (ZERO_IS_NONE_PROPS.has(propNormalized) && (value === '0' || value === 'none')) {
if (ZERO_IS_NONE_PROPS.has(propNormalized) && value === '0') {
value = 'none';
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/scopes.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function describeScope(container) {
// and merging those two boundaries would report duplicates across unrelated
// scopes. `JSON.stringify` over the segment array can’t be spelled by
// the segments.
export function scopeKey(container) {
function scopeKey(container) {
if (container.type === 'root') return 'root';
return JSON.stringify(scopeSegments(container));
}
Expand Down Expand Up @@ -156,7 +156,7 @@ export function atRuleLabel(atrule) {
// whitespace-insensitive—`.b, .a` matches the same elements with the same
// specificities as `.a, .b`. Whitespace is only collapsed outside quotes:
// `[data-x="a b"]` and `[data-x="a b"]` are different selectors.
export function selectorSetKey(rule) {
function selectorSetKey(rule) {
return splitSelectors(rule.selector)
.map(selector => selector.replace(/("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')|\s+/g, (match, quoted) => quoted ?? ' '))
.sort()
Expand Down
2 changes: 1 addition & 1 deletion src/lib/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { memoized } from './util.js';
// One depth/quote/escape-aware scan of a selector list, answering both
// questions anything here asks of it: where its top-level commas are, and
// whether the first is followed by whitespace. Memoized per run (see
// `src/caches.js`)—the same handful of selector strings get split repeatedly
// caches.js)—the same handful of selector strings get split repeatedly
// within a run (eligibility filtering, merge-safety scans, occurrence
// reporting), but selector text is unbounded across runs.
//
Expand Down
2 changes: 1 addition & 1 deletion src/lib/shorthands.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// `margin-inline-start`): which physical side a logical longhand resolves to
// depends on writing mode, which static CSS can’t tell. Not exhaustive; extend
// as needed.
export const SHORTHAND_LONGHANDS = {
const SHORTHAND_LONGHANDS = {
margin: [
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'margin-block', 'margin-block-start', 'margin-block-end',
Expand Down
7 changes: 2 additions & 5 deletions src/lib/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export function snapshot(root, scope, rules) {
// itself is state a rollback has to restore
scopeRules: scope.rules.slice(),
containers: affectedContainers(rules).map(container => ({ container, nodes: container.nodes.slice() })),
rules: rules.map(rule => ({ entry: captureNode(rule), parent: rule.parent })),
rules: rules.map(captureNode),
};
}

Expand Down Expand Up @@ -280,10 +280,7 @@ export function rollback(snap, scope) {
forgetSeparator(container);
}

for (const { entry, parent } of snap.rules) {
restoreNode(entry);
entry.node.parent = parent;
}
for (const entry of snap.rules) restoreNode(entry);

scope.rules.length = 0;
scope.rules.push(...snap.scopeRules);
Expand Down
20 changes: 10 additions & 10 deletions src/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function removeRedundantDuplicates(ctx, container, scopeLabel, selectors)

const last = decls.at(-1);
const value = shortestValue(decls);
if (last.value !== value) last.value = value;
last.value = value;

for (const decl of decls) {
if (decl !== last) decl.remove();
Expand Down Expand Up @@ -104,7 +104,7 @@ function isOverlappingExtra(ctx, node, propNormalized, excludeKey) {
//
// `exemptRules` covers the group’s whole entangled cluster—a fellow member is
// being absorbed into the same coordinated merge, not staying behind.
export function findBlockingRule(ctx, scope, distinctRules, exemptRules, firstIndex, lastIndex, propNormalized) {
function findBlockingRule(ctx, scope, distinctRules, exemptRules, firstIndex, lastIndex, propNormalized) {
// Only rules actually inside the span can block; walking the whole scope to
// skip everything outside it made this quadratic in the scope’s rule count.
// The group’s own selector list is likewise only needed once something in
Expand Down Expand Up @@ -204,7 +204,7 @@ export function foldSameSelectorRules(ctx, scope) {
// declarations always need to move, overlapping or not, while a non-target
// occurrence’s extra only needs to move if it overlaps the shared property and
// was declared after it.
export function mergeSoloGroup(ctx, scope, group) {
function mergeSoloGroup(ctx, scope, group) {
const { key, occurrences, distinctRules, propNormalized } = group;
const target = distinctRules.at(-1);

Expand Down Expand Up @@ -238,7 +238,7 @@ export function mergeSoloGroup(ctx, scope, group) {

const value = shortestValue(occurrenceDecls(occurrences));
const targetDecl = declFor(occurrences, target);
if (targetDecl.value !== value) targetDecl.value = value;
targetDecl.value = value;

for (const rule of distinctRules) {
if (rule === target) continue;
Expand Down Expand Up @@ -294,7 +294,7 @@ export function mergeSoloGroup(ctx, scope, group) {
//
// Solo groups only—in a multi-group cluster a partial merge could relocate a
// shared rule’s selector out from under the other groups.
export function mergePartialGroup(ctx, scope, group, reason) {
function mergePartialGroup(ctx, scope, group, reason) {
const { key, occurrences, distinctRules, propNormalized } = group;
const exempt = new Set(distinctRules);

Expand Down Expand Up @@ -362,7 +362,7 @@ function mergeTwinRules(ctx, scope, cluster, ruleKeyCounts) {
const key = ctx.keyOf(decl);
const group = cluster.find(candidate => candidate.key === key);
const value = shortestValue(occurrenceDecls(group.occurrences));
if (decl.value !== value) decl.value = value;
decl.value = value;

ctx.applied.push({ scope: scope.label, key, selectors: mergedSelectors, value });
}
Expand Down Expand Up @@ -433,7 +433,7 @@ function mergeClusterGroupRuns(ctx, scope, group) {
mergedRule.selector = joinSelectors(mergedSelectors, lastRule, ctx.multilineSelectors, ctx.spacedCommas);
const lastDecl = declFor(runOccurrences, lastRule);
lastDecl.remove();
if (lastDecl.value !== value) lastDecl.value = value;
lastDecl.value = value;
mergedRule.append(lastDecl);
lastRule.after(mergedRule);
recordInsertion(mergedRule);
Expand Down Expand Up @@ -501,7 +501,7 @@ function splitStarHub(ctx, scope, cluster, hub) {
));

const value = shortestValue(occurrenceDecls(occurrences));
if (sharedDecl.value !== value) sharedDecl.value = value;
sharedDecl.value = value;

const anchorRule = hub.clone({ nodes: [] });
anchorRule.selector = joinSelectors(mergedSelectors, hub, ctx.multilineSelectors, ctx.spacedCommas);
Expand Down Expand Up @@ -587,7 +587,7 @@ function mergeCluster(ctx, scope, cluster) {
hubCandidates++;
}
}
const isStar = hub !== null && hubCandidates === 1 && [...ruleKeyCounts.values()].every(keysHere => (
const isStar = hubCandidates === 1 && [...ruleKeyCounts.values()].every(keysHere => (
keysHere.size === clusterSize || keysHere.size === 1
));

Expand Down Expand Up @@ -661,7 +661,7 @@ function gated(ctx, scope, keys, rules, apply) {
// block behind.
ctx.settle();

const snap = snapshot(ctx.root, scope, [...new Set(rules)]);
const snap = snapshot(ctx.root, scope, rules);
const appliedBefore = ctx.applied.length;

apply();
Expand Down
4 changes: 2 additions & 2 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ export default function cssdedup(options = {}) {
root.warn(result, `${withheld.count} merge${withheld.count !== 1 ? 's' : ''} withheld (\`savingsOnly\`): applying ${withheld.count !== 1 ? 'them' : 'it'} would make the style sheet ${Math.abs(withheld.bytes.saved)} bytes bigger`);
}
for (const item of skipped) {
root.warn(result, `Duplicate \`${item.key}\` left unmerged (${item.scope === 'root' ? 'root' : item.scope}): ${item.reason}`);
root.warn(result, `Duplicate \`${item.key}\` left unmerged (${item.scope}): ${item.reason}`);
}
return;
}

const { findings } = analyzeRoot(root, options);
for (const finding of findings) {
if (finding.repeated) {
root.warn(result, `Selector \`${finding.key}\` written ${finding.occurrences.length} times in its scope (${finding.scope === 'root' ? 'root' : finding.scope})`);
root.warn(result, `Selector \`${finding.key}\` written ${finding.occurrences.length} times in its scope (${finding.scope})`);
continue;
}

Expand Down
Loading