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, );