75 lines
4.5 KiB
JavaScript
75 lines
4.5 KiB
JavaScript
// Гейт «одно место для цвета и размера», половина в CSS (STACK_DECISIONS §3).
|
||
// TSX-половина — в eslint.config.js.
|
||
//
|
||
// Двухслойно: запрет литеральных цветов ловит их в ЛЮБОМ свойстве, включая сокращённые
|
||
// записи вроде `border: 1px solid #fff`, а allowed-list добавляет свойства, где литерал
|
||
// не цвет (`font-size`, `z-index`). reportDisables делает ошибкой и попытку отключить
|
||
// правило комментарием.
|
||
const gate = { reportDisables: true };
|
||
const token = '/^var\\(--/';
|
||
const colorValues = [token, 'inherit', 'currentColor', 'currentcolor', 'transparent'];
|
||
|
||
// Ф-4: шкала отступов обкатана оболочкой S2, гейт включён. Значение — последовательность токенов;
|
||
// 0 и auto легальны, потому что это не размеры, а отсутствие размера и «посчитай сам».
|
||
// calc() намеренно не разрешён: составное выражение из литералов проверить нечем, и если размер
|
||
// понадобился новый, его место — в tokens.css, а не в скобках calc.
|
||
const spacingValues = ['/^(?:(?:var\\(--[a-z0-9-]+\\)|0|auto)\\s*)+$/', 'inherit'];
|
||
|
||
// Системные цветовые ключевые слова — не «именованные цвета», их не видит color-named, и в
|
||
// сокращённых записях (outline, box-shadow, border) их не ловит вообще ничто (дыра из Ф-9).
|
||
const systemColors =
|
||
'/\\b(AccentColor|AccentColorText|ActiveText|ButtonBorder|ButtonFace|ButtonText|Canvas|CanvasText|Field|FieldText|GrayText|Highlight|HighlightText|LinkText|Mark|MarkText|SelectedItem|SelectedItemText|VisitedText)\\b/i';
|
||
|
||
export default {
|
||
extends: ['stylelint-config-standard'],
|
||
// reportDisables на правиле ловит только ИМЕНОВАННОЕ отключение
|
||
// (`/* stylelint-disable color-no-hex */`). Голое `/* stylelint-disable */` в шапке файла
|
||
// снимало гейт целиком и молча — проверено. Эта опция требует, чтобы отключение всегда
|
||
// называло правило, и тем самым закрывает обход.
|
||
reportUnscopedDisables: true,
|
||
rules: {
|
||
'color-no-hex': [true, gate],
|
||
'color-named': ['never', gate],
|
||
// color() и light-dark() тоже задают цвет литералом, а в сокращённой записи
|
||
// (`border: 1px solid color(...)`) их не ловит ничто другое. color-mix() оставлен:
|
||
// смешивать ему нечего, кроме токенов.
|
||
'function-disallowed-list': [
|
||
['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color', 'light-dark'],
|
||
gate,
|
||
],
|
||
'declaration-property-value-allowed-list': [
|
||
{
|
||
color: colorValues,
|
||
'background-color': colorValues,
|
||
'border-color': colorValues,
|
||
background: [...colorValues, 'none'],
|
||
fill: [...colorValues, 'none'],
|
||
stroke: [...colorValues, 'none'],
|
||
'font-size': [token, 'inherit'],
|
||
'z-index': [token],
|
||
'/^(padding|margin)(-|$)/': spacingValues,
|
||
'/^(gap|row-gap|column-gap)$/': spacingValues,
|
||
'/^border-.*radius$/': spacingValues,
|
||
},
|
||
{ ...gate, message: 'Значение только из токена: var(--...) из src/tokens/tokens.css' },
|
||
],
|
||
'declaration-property-value-disallowed-list': [
|
||
{ '/.*/': [systemColors] },
|
||
{ ...gate, message: 'Системный цвет ОС — не наш цвет: тон берётся из src/tokens/tokens.css' },
|
||
],
|
||
// CSS Modules читаются из TS как styles.panelHeader, поэтому классы camelCase.
|
||
'selector-class-pattern': ['^[a-z][a-zA-Z0-9]*$', { message: 'Имя класса — camelCase' }],
|
||
},
|
||
overrides: [
|
||
{
|
||
// Единственное место, где литеральный цвет легален: сами токены.
|
||
files: ['src/tokens/tokens.css'],
|
||
rules: {
|
||
'color-no-hex': null,
|
||
'color-named': null,
|
||
'function-disallowed-list': null,
|
||
'declaration-property-value-allowed-list': null,
|
||
},
|
||
},
|
||
],
|
||
};
|