textmachine/frontend/eslint.config.js

78 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';
import tseslint from 'typescript-eslint';
// Гейт «одно место для цвета и размера», половина в TSX (STACK_DECISIONS §3).
// CSS-половина — в stylelint.config.js.
const colorLiteral =
'Цвет живёт только в src/tokens/tokens.css. В коде — var(--color-...) из .module.css.';
const inlineStyle =
'Инлайновый стиль запрещён: размеры и цвета берутся из .module.css на токенах. ' +
"Единственное исключение — передача CSS-переменной: style={{ '--progress': value }}.";
const tokenGate = [
{ selector: 'Literal[value=/#[0-9a-fA-F]{3}\\b/]', message: colorLiteral },
{ selector: 'Literal[value=/#[0-9a-fA-F]{6}\\b/]', message: colorLiteral },
{ selector: 'Literal[value=/#[0-9a-fA-F]{8}\\b/]', message: colorLiteral },
{ selector: 'Literal[value=/\\b(rgba?|hsla?|oklch|oklab|lab|lch)\\(/]', message: colorLiteral },
{ selector: 'TemplateElement[value.raw=/#[0-9a-fA-F]{3,8}\\b/]', message: colorLiteral },
// Ключ-идентификатор — обычное свойство (color, width); CSS-переменная синтаксически
// обязана быть строковым ключом, поэтому проходит только она.
{
selector: "JSXAttribute[name.name='style'] ObjectExpression > Property[key.type='Identifier']",
message: inlineStyle,
},
{
selector:
"JSXAttribute[name.name='style'] ObjectExpression > Property[key.type='Literal'][key.value!=/^--/]",
message: inlineStyle,
},
{
selector: "JSXAttribute[name.name='style'] > JSXExpressionContainer > Identifier",
message: inlineStyle,
},
];
export default tseslint.config(
{ ignores: ['dist/**', '.shots/**', '.tooling/**'] },
js.configs.recommended,
tseslint.configs.recommended,
// .flat — именно flat-вариант: одноимённый ключ верхнего уровня остался в старом формате.
reactHooks.configs.flat['recommended-latest'],
reactRefresh.configs.vite,
{
languageOptions: {
globals: { ...globals.browser, ...globals.node },
},
rules: {
'no-restricted-syntax': ['error', ...tokenGate],
// Глобальных стилей ровно два файла; всё остальное — CSS Modules, они скоупятся сами.
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['**/*.css', '!**/*.module.css'],
message:
'Глобальный CSS импортируется только в src/main.tsx. Экранам и примитивам — *.module.css.',
},
],
},
],
},
},
{
// Точка сборки глобальных стилей: сброс, токены и шрифты подключаются здесь и больше нигде.
files: ['src/main.tsx'],
rules: { 'no-restricted-imports': 'off' },
},
{
// Контракт-тест обязан называть замеренные цвета в лицо — иначе ему нечего сверять.
files: ['src/tokens/*.test.ts'],
rules: { 'no-restricted-syntax': 'off' },
},
prettier,
);