core/stylelint/shrinkable-grid-track.stylelint.rule.ts
core/stylelint/shrinkable-grid-track.stylelint.rule.ts is a file in GovLab Quality. 57 lines of code and 11 definitions.
import type { Declaration, Root } from "postcss";
import stylelint, { type PostcssResult, type Rule } from "stylelint";
import type { GovlabStylelintMeta } from "#types/stylelint.types";
import valueParser, { type Node } from "postcss-value-parser";
import { withRuleId } from "#core/formatters/stylelint.formatter";
const { createPlugin, utils } = stylelint;
const ruleName = "govlab/shrinkable-grid-track";
const ruleId = "shrinkable_grid_track";
export const RULE_META: GovlabStylelintMeta = {
meta: {
canonical: ["responsive-design"],
description: "Require flexible grid column tracks to be able to shrink below their content",
fixable: false,
},
ruleId,
ruleName,
};
const TRACK_PROPS = new Set(["grid-template-columns", "grid-auto-columns"]);
const FLEX_UNIT = "fr";
const SHRINKING_FUNCTION = "minmax";
const messages = utils.ruleMessages(ruleName, {
bareFraction: (track: string): string =>
withRuleId(
`The column track '${track}' is a bare fraction, which never shrinks below its content, so one long word or code line pushes the grid wider than a narrow screen. Write the track as minmax(0, ${track}) so it can shrink to the space it is given.`,
ruleId,
),
});
const bareFractions = function bareFractions(nodes: readonly Node[], shrinkable: boolean): string[] {
return nodes.flatMap((node) => {
if (node.type === "function") {
return bareFractions(node.nodes, shrinkable || node.value.toLowerCase() === SHRINKING_FUNCTION);
}
if (node.type !== "word" || shrinkable) {
return [];
}
const parsed = valueParser.unit(node.value);
return parsed !== false && parsed.unit.toLowerCase() === FLEX_UNIT ? [node.value] : [];
});
};
const rule: Rule = Object.assign(
(primary: unknown) =>
(root: Root, result: PostcssResult): void => {
if (primary !== true) {
return;
}
root.walkDecls((decl: Declaration) => {
if (!TRACK_PROPS.has(decl.prop.toLowerCase())) {
return;
}
for (const track of new Set(bareFractions(valueParser(decl.value).nodes, false))) {
utils.report({ message: messages.bareFraction(track), node: decl, result, ruleName, word: track });
}
});
},
{ messages, ruleName },
);
export default createPlugin(ruleName, rule);