Files
LearnNode/node-course/node_modules/@eslint/css/dist/rules/no-invalid-named-grid-areas.js
T
2026-08-06 12:00:39 +10:00

137 lines
5.6 KiB
JavaScript

/**
* @fileoverview Rule to prevent invalid named grid areas in CSS grid templates.
* @author xbinaryx
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
* @import { CSSRuleDefinition } from "../types.js"
* @typedef {"emptyGridArea" | "unevenGridArea" | "nonRectangularGridArea"} NoInvalidNamedGridAreasMessageIds
* @typedef {CSSRuleDefinition<{ RuleOptions: [], MessageIds: NoInvalidNamedGridAreasMessageIds }>} NoInvalidNamedGridAreasRuleDefinition
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Regular expression to match null cell tokens (sequences of one or more dots)
*/
const nullCellToken = /^\.+$/u;
/**
* Finds non-rectangular grid areas in a 2D grid
* @param {string[][]} grid 2D array representing the grid areas
* @returns {Array<{name: string, row: number}>} Array of errors found
*/
function findNonRectangularAreas(grid) {
const errors = [];
const reported = new Set();
const names = [...new Set(grid.flat())].filter(name => !nullCellToken.test(name));
for (const name of names) {
const indicesByRow = grid.map(row => {
const indices = [];
let idx = row.indexOf(name);
while (idx !== -1) {
indices.push(idx);
idx = row.indexOf(name, idx + 1);
}
return indices;
});
for (let i = 0; i < indicesByRow.length; i++) {
for (let j = i + 1; j < indicesByRow.length; j++) {
const row1 = indicesByRow[i];
const row2 = indicesByRow[j];
if (row1.length === 0 || row2.length === 0) {
continue;
}
if (row1.length !== row2.length ||
!row1.every((val, idx) => val === row2[idx])) {
const key = `${name}|${j}`;
if (!reported.has(key)) {
errors.push({ name, row: j });
reported.add(key);
}
}
}
}
}
return errors;
}
const validProps = new Set(["grid-template-areas", "grid-template", "grid"]);
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoInvalidNamedGridAreasRuleDefinition} */ ({
meta: {
type: "problem",
docs: {
description: "Disallow invalid named grid areas",
recommended: true,
url: "https://github.com/eslint/css/blob/main/docs/rules/no-invalid-named-grid-areas.md",
},
messages: {
emptyGridArea: "Grid area must contain at least one cell token.",
unevenGridArea: "Grid area strings must have the same number of cell tokens.",
nonRectangularGridArea: "Cell tokens with name '{{name}}' must form a rectangle.",
},
},
create(context) {
return {
Declaration(node) {
const propName = node.property.toLowerCase();
if (validProps.has(propName) &&
node.value.type === "Value" &&
node.value.children.length > 0) {
const stringNodes = node.value.children.filter(child => child.type === "String");
if (stringNodes.length === 0) {
return;
}
const grid = [];
const emptyNodes = [];
const unevenNodes = [];
let firstRowLen = null;
for (const stringNode of stringNodes) {
const trimmedValue = stringNode.value.trim();
if (trimmedValue === "") {
emptyNodes.push(stringNode);
continue;
}
const row = trimmedValue.split(" ").filter(Boolean);
grid.push(row);
if (firstRowLen === null) {
firstRowLen = row.length;
}
else if (row.length !== firstRowLen) {
unevenNodes.push(stringNode);
}
}
if (emptyNodes.length > 0) {
emptyNodes.forEach(emptyNode => context.report({
node: emptyNode,
messageId: "emptyGridArea",
}));
return;
}
if (unevenNodes.length > 0) {
unevenNodes.forEach(unevenNode => context.report({
node: unevenNode,
messageId: "unevenGridArea",
}));
return;
}
const nonRectErrors = findNonRectangularAreas(grid);
nonRectErrors.forEach(({ name, row }) => {
const stringNode = stringNodes[row];
context.report({
node: stringNode,
messageId: "nonRectangularGridArea",
data: {
name,
},
});
});
}
},
};
},
});