first commit
This commit is contained in:
+136
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @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,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user