import path from 'node:path'; import js from '@eslint/js'; import { includeIgnoreFile } from '@eslint/compat'; import { defineConfig } from 'eslint/config'; import prettier from 'eslint-config-prettier'; import importX, { createNodeResolver } from 'eslint-plugin-import-x'; import htmlEslint from '@html-eslint/eslint-plugin'; import htmlParser from '@html-eslint/parser'; import defaultAttrs from './tools/build-icons/render/default-attrs.json' with { type: 'json' }; import tseslint from 'typescript-eslint'; const gitignorePath = path.join(import.meta.dirname, '.gitignore'); export default defineConfig([ tseslint.configs.recommended, { // `packages/angular` has its own config with its own tsconfig, so a single `eslint .` run sees // two candidate roots and typescript-eslint refuses to guess between them. languageOptions: { parserOptions: { tsconfigRootDir: import.meta.dirname, }, }, }, // Everything git ignores (generated icon sources, build output, caches) is ignored here too. includeIgnoreFile(gitignorePath), { // Ignores that are not in .gitignore, because these files are committed. ignores: [ 'lib', '**/tests', 'packages/**/tests/*', 'docs/images', 'docs/**/examples/', 'docs/.vitepress/theme/components/editors/preact/index.js', 'packages/svelte/.svelte-kit', // Tracked in git despite matching a .gitignore pattern, so lint it. '!packages/lucide-react/dynamicIconImports.mjs', ], }, { files: ['**/*.js'], languageOptions: { ecmaVersion: 'latest', sourceType: 'module', globals: { console: 'readonly', process: 'readonly', global: 'readonly', __dirname: 'readonly', }, }, plugins: { 'import-x': importX, }, settings: { 'import-x/resolver-next': [createNodeResolver()], }, rules: { ...js.configs.recommended.rules, ...prettier.rules, 'no-console': 'off', 'no-param-reassign': 'off', 'no-shadow': 'off', 'no-use-before-define': 'off', 'import-x/no-extraneous-dependencies': [ 'error', { devDependencies: [ '**/*.test.js', '**/*.spec.js', '**/scripts/**', 'eslint.config.js', 'packages/**/tests/**', ], }, ], 'import-x/extensions': [ 'error', { pattern: { mjs: 'always', json: 'always', }, }, ], }, }, { rules: { // Omitting a property by destructuring it away (`const { key, ...attrs } = node`) leaves the // omitted binding unused on purpose, so don't report it. '@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }], }, }, { files: ['./icons/*.svg'], languageOptions: { parser: htmlParser, }, plugins: { '@html-eslint': htmlEslint, }, rules: { '@html-eslint/require-doctype': 'off', '@html-eslint/no-duplicate-attrs': 'error', '@html-eslint/no-inline-styles': 'error', '@html-eslint/require-attrs': [ 'error', ...Object.entries(defaultAttrs).map(([attr, value]) => ({ tag: 'svg', attr, value: String(value), })), ], '@html-eslint/indent': ['error', 2], '@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }], '@html-eslint/no-extra-spacing-attrs': [ 'error', { enforceBeforeSelfClose: true, }, ], '@html-eslint/attrs-newline': [ 'error', { inline: ['path', 'line', 'polyline', 'polygon', 'rect', 'circle', 'ellipse'], }, ], '@html-eslint/require-closing-tags': [ 'error', { selfClosing: 'always', // Inside every tag counts as "foreign", where the rule only checks that a tag // already written `/>` stays that way. Listing the shapes here is what actually forces // `` to become ``. selfClosingCustomPatterns: ['^(path|line|polyline|polygon|rect|circle|ellipse)$'], }, ], '@html-eslint/no-restricted-attr-values': [ 'error', { attrPatterns: ['^(fill|stroke)$'], attrValuePatterns: ['^(?!(none|currentColor)$).*$'], message: 'Icons must inherit their colors: `fill` and `stroke` may only be `none` or `currentColor`.', }, ], '@html-eslint/element-newline': 'error', '@html-eslint/no-trailing-spaces': 'error', '@html-eslint/quotes': 'error', }, }, ]);