forked from stylelint/stylelint
/
index.js
135 lines (106 loc) · 2.92 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// @ts-nocheck
'use strict';
const _ = require('lodash');
const report = require('../../utils/report');
const ruleMessages = require('../../utils/ruleMessages');
const validateOptions = require('../../utils/validateOptions');
const valueParser = require('postcss-value-parser');
const ruleName = 'named-grid-areas-no-invalid';
const messages = ruleMessages(ruleName, {
noRows: () => 'no rows defined',
notRectangular: () => 'template is not rectangular',
notContiguousOrRectangular: (areaNames) =>
`The following areas are not contiguous or rectangular ${areaNames.join(', ')}`,
});
function rule(actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual });
if (!validOptions) {
return;
}
root.walkDecls('grid-template-areas', (decl) => {
const value = decl.value;
const parsedValues = valueParser(value).nodes;
if (
parsedValues.length > 0 &&
parsedValues[0].type === 'word' &&
parsedValues[0].value === 'none'
) {
return;
}
const areas = areasArray(parsedValues);
if (_.isEmpty(areas) || _.isEmpty(areas[0])) {
report({
message: messages.noRows(),
node: decl,
result,
ruleName,
});
return;
}
if (!isRectangular(areas)) {
report({
message: messages.notRectangular(),
node: decl,
result,
ruleName,
});
}
const notContiguousOrRectangular = findNotContiguousOrRectangular(areas);
if (!_.isEmpty(notContiguousOrRectangular)) {
report({
message: messages.notContiguousOrRectangular(notContiguousOrRectangular),
node: decl,
result,
ruleName,
});
}
});
};
}
function areasArray(parsedValues) {
const gridTemplateRows = _.filter(parsedValues, { type: 'string' });
const rows = gridTemplateRows.map((v) => v.value);
return rows.map((row) => _.compact(row.trim().split(' ')));
}
function columnsPerRow(areas) {
return areas.map((row) => row.length);
}
function isRectangular(areas) {
return columnsPerRow(areas).every((val, i, arr) => val === arr[0]);
}
function isContiguousAndRectangular(areas, name) {
const indicesByRow = areas.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++) {
if (indicesByRow[i].length === 0 || indicesByRow[j].length === 0) {
continue;
}
if (!_.isEqual(indicesByRow[i], indicesByRow[j])) {
return false;
}
}
}
return true;
}
function namedAreas(areas) {
const names = new Set(_.flatten(areas));
names.delete('.');
return Array.from(names);
}
function findNotContiguousOrRectangular(areas) {
return Array.from(
new Set(namedAreas(areas).filter((name) => !isContiguousAndRectangular(areas, name))),
);
}
rule.ruleName = ruleName;
rule.messages = messages;
module.exports = rule;