@enke.dev/lint 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,16 @@ npm i -D jiti typescript typescript-eslint
21
21
 
22
22
  ## Prepare Eslint config
23
23
 
24
- Create a `eslint.config.js` (or `eslint.config.ts`) file in the root of your project and add the following content:
24
+ Create a `eslint.config.js` (or `eslint.config.ts`) file in the root of your project. There are three ways to consume the configuration, from least to most control: the legacy all-in-one config, a ready-made preset, or the individual single configs.
25
+
26
+ > [!NOTE]
27
+ > Using Typescript may require the root directory where to find the `tsconfig.json` to be specified.\
28
+ > Therefore, a convenience function `setTsConfigRootDir` is provided to configure this option globally.
29
+
30
+ ### 1. Legacy config
31
+
32
+ > [!WARNING]
33
+ > The default export is **deprecated**. It bundles `node` + `lit` + `html` + `json` and additionally assumes gitignore-based ignores and this package's own tsconfig root — assumptions that do not hold for every project. Prefer a [preset](#2-presets) or [single configs](#3-single-configs).
25
34
 
26
35
  ```js
27
36
  import config from '@enke.dev/lint';
@@ -29,27 +38,90 @@ import config from '@enke.dev/lint';
29
38
  export default config;
30
39
  ```
31
40
 
32
- > [!NOTE]
33
- > Using Typescript may requires the root directory where to find the `tsconfig.json` to be specified.\
34
- > Therefore, a convenience function `setTsConfigRootDir` is provided to configure this option globally.
41
+ ### 2. Presets
42
+
43
+ Ready-made combinations for common project types. Import the one that matches your project and export it directly:
44
+
45
+ ```ts
46
+ import { frontend } from '@enke.dev/lint/eslint/presets/frontend';
47
+
48
+ export default frontend;
49
+ ```
50
+
51
+ Available presets:
52
+
53
+ | Preset | Import name | Bundles |
54
+ | -------------------------------------------- | ------------- | -------------------------------- |
55
+ | `@enke.dev/lint/eslint/presets/frontend` | `frontend` | `node` + `lit` + `html` + `json` |
56
+ | `@enke.dev/lint/eslint/presets/node-library` | `nodeLibrary` | `node` + `json` |
57
+ | `@enke.dev/lint/eslint/presets/bun-library` | `bunLibrary` | `bun` + `json` |
58
+
59
+ Presets are flat config arrays, so they can be extended — e.g. to point the TypeScript parser at your `tsconfig.json`:
60
+
61
+ ```ts
62
+ import { defineConfig } from 'eslint/config';
63
+
64
+ import { setTsConfigRootDir } from '@enke.dev/lint';
65
+ import { frontend } from '@enke.dev/lint/eslint/presets/frontend';
35
66
 
36
- ### Extending a config
67
+ export default defineConfig([...frontend, setTsConfigRootDir(import.meta.dirname)]);
68
+ ```
69
+
70
+ ### 3. Single configs
37
71
 
38
- For example for setting up the Typescript parser, you can extend the base config like this:
72
+ For full control, compose the individual chunks yourself. Each is a flat config array; spread the ones you need:
39
73
 
40
74
  ```ts
41
- import config, { setTsConfigRootDir } from '@enke.dev/lint';
75
+ import { defineConfig } from 'eslint/config';
76
+
77
+ import { bun } from '@enke.dev/lint/eslint/bun';
78
+ import { html } from '@enke.dev/lint/eslint/html';
79
+ import { json } from '@enke.dev/lint/eslint/json';
80
+ import { lit } from '@enke.dev/lint/eslint/lit';
42
81
 
43
82
  export default defineConfig([
44
- // extend the base config
45
- ...config,
46
- // configure typescript parser to your needs
47
- setTsConfigRootDir(import.meta.dirname),
83
+ ...bun, // TypeScript + import rules, bun module resolver
84
+ ...lit, // web components, lit and inline-html linting
85
+ ...html, // standalone .html files
86
+ ...json, // .json files
48
87
  ]);
49
88
  ```
50
89
 
51
- > [!TIP]
52
- > Extending configurations works the same way with all other configs provided by this package.
90
+ Available configs:
91
+
92
+ | Import | Name | Applies to | Notes |
93
+ | ---------------------------- | ------ | ----------------- | --------------------------------------------------------------- |
94
+ | `@enke.dev/lint/eslint/node` | `node` | `**/*.js`, `*.ts` | Base TS/import rules, node resolver. |
95
+ | `@enke.dev/lint/eslint/bun` | `bun` | `**/*.js`, `*.ts` | Same base, bun resolver (resolves `bun:test`, `bun:sqlite`, …). |
96
+ | `@enke.dev/lint/eslint/lit` | `lit` | `**/*.js`, `*.ts` | Web components, lit, a11y and inline-html rules. |
97
+ | `@enke.dev/lint/eslint/html` | `html` | `**/*.html` | Standalone HTML files. |
98
+ | `@enke.dev/lint/eslint/json` | `json` | `**/*.json` | JSON files. |
99
+
100
+ > [!NOTE]
101
+ > `node` and `bun` differ only by the import resolver — pick one.
102
+
103
+ ### Type-aware linting
104
+
105
+ By default the rules are syntactic only (typescript-eslint `strict` + `stylistic`), so no `tsconfig` is required. Every TypeScript base — the `node` / `bun` single configs and all three presets — additionally ships a **type-checked** variant that enables the type-aware rule sets (`strictTypeChecked` + `stylisticTypeChecked`, e.g. `no-floating-promises`, `no-misused-promises`, `await-thenable`).
106
+
107
+ Each variant is an extra export from the same module — just add the `TypeChecked` suffix:
108
+
109
+ | Base | Type-checked variant | Import from |
110
+ | ------------- | ------------------------ | -------------------------------------------- |
111
+ | `node` | `nodeTypeChecked` | `@enke.dev/lint/eslint/node` |
112
+ | `bun` | `bunTypeChecked` | `@enke.dev/lint/eslint/bun` |
113
+ | `frontend` | `frontendTypeChecked` | `@enke.dev/lint/eslint/presets/frontend` |
114
+ | `nodeLibrary` | `nodeLibraryTypeChecked` | `@enke.dev/lint/eslint/presets/node-library` |
115
+ | `bunLibrary` | `bunLibraryTypeChecked` | `@enke.dev/lint/eslint/presets/bun-library` |
116
+
117
+ ```ts
118
+ import { frontendTypeChecked } from '@enke.dev/lint/eslint/presets/frontend';
119
+
120
+ export default frontendTypeChecked;
121
+ ```
122
+
123
+ > [!WARNING]
124
+ > Type-aware rules require a TypeScript program. The variants enable `projectService`, which auto-discovers the nearest `tsconfig.json` — but any file **not** covered by one (stray configs, generated JS) will error unless you configure [`parserOptions.projectService.allowDefaultProject`](https://typescript-eslint.io/packages/parser/#projectservice). Type-aware linting is also noticeably slower.
53
125
 
54
126
  ### Using Monorepos
55
127
 
@@ -96,11 +168,19 @@ For now, [no TypeScript support](https://github.com/stylelint/stylelint/issues/4
96
168
 
97
169
  ## Development
98
170
 
171
+ Prerequisites:
172
+
173
+ - **Node** — version pinned in [`.node-version`](.node-version) (use `nvm`/`fnm` to match it).
174
+ - **[bun](https://bun.sh/)** — required for the `test:bun` suite; install it globally (`curl -fsSL https://bun.sh/install | bash`).
175
+
99
176
  This repo self-tests the configuration by linting itself: `npm run lint`.\
100
177
  Therefore, a `test.eslint.config.ts` is used.
101
178
 
102
- And additionally, a naive test is in place to check that the linter actually finds issues: `npm run test`.\
103
- It uses the native Node test runner against some obviously faulty code in the `test` directory.
179
+ And additionally, naive tests are in place to check that the linter actually finds issues: `npm run test`.\
180
+ This runs two suites:
181
+
182
+ - `npm run test:node` — the native Node test runner against obviously faulty code in the `test` directory (default/legacy config, prettier, stylelint).
183
+ - `npm run test:bun` — the bun test runner (`test.run.bun.ts`), which exercises the `bun` and type-checked configs against the source directly (bun resolves `.js`→`.ts`, so no build is needed). Requires [bun](https://bun.sh/).
104
184
 
105
185
  ### Updating dependencies
106
186
 
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from './typescript.config.js';
2
+ export declare const bun: FlatConfig;
3
+ export declare const bunTypeChecked: FlatConfig;
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
3
+ import { typescript } from './typescript.config.js';
4
+ // Same base as `node`, but with the bun module resolver so builtins such as
5
+ // `bun:test` and `bun:sqlite` resolve instead of being flagged as unresolved.
6
+ export const bun = defineConfig([eslintPluginPrettierRecommended, typescript('bun')]);
7
+ // Same as `bun`, plus type-aware rules (needs a tsconfig; see `TypescriptOptions`).
8
+ export const bunTypeChecked = defineConfig([
9
+ eslintPluginPrettierRecommended,
10
+ typescript('bun', { typeChecked: true }),
11
+ ]);
@@ -0,0 +1,2 @@
1
+ import type { FlatConfig } from './typescript.config.js';
2
+ export declare const html: FlatConfig;
@@ -0,0 +1,43 @@
1
+ import eslintPluginHtml from '@html-eslint/eslint-plugin';
2
+ import eslintParserHtml from '@html-eslint/parser';
3
+ import { defineConfig } from 'eslint/config';
4
+ import eslintPluginHtmlScripts from 'eslint-plugin-html';
5
+ import { parserOptions } from './typescript.config.js';
6
+ // Standalone HTML files.
7
+ const htmlConfig = {
8
+ files: ['**/*.html'],
9
+ plugins: {
10
+ html: eslintPluginHtml,
11
+ htmlScripts: eslintPluginHtmlScripts,
12
+ },
13
+ extends: ['html/recommended'],
14
+ language: 'html/html',
15
+ languageOptions: {
16
+ parser: eslintParserHtml,
17
+ parserOptions,
18
+ },
19
+ settings: {
20
+ 'html/indent': '+2',
21
+ 'html/lowercase': 'error',
22
+ 'html/no-accesskey-attrs': 'error',
23
+ 'html/no-aria-hidden-body': 'error',
24
+ 'html/no-aria-hidden-on-focusable': 'error',
25
+ 'html/no-duplicate-class': 'error',
26
+ 'html/no-duplicate-in-head': 'error',
27
+ 'html/no-empty-headings': 'error',
28
+ 'html/no-invalid-entities': 'error',
29
+ 'html/no-heading-inside-button': 'error',
30
+ 'html/no-invalid-entity': 'error',
31
+ 'html/no-invalid-role': 'error',
32
+ 'html/no-nested-interactive': 'error',
33
+ 'html/no-multiple-empty-lines': 'error',
34
+ 'html/no-target-blank': 'error',
35
+ 'html/no-trailing-spaces': 'error',
36
+ 'html/report-bad-indent': 'error',
37
+ 'html/require-input-label': 'error',
38
+ },
39
+ rules: {
40
+ 'html/indent': ['error', 2],
41
+ },
42
+ };
43
+ export const html = defineConfig([htmlConfig]);
@@ -0,0 +1,2 @@
1
+ import type { FlatConfig } from './typescript.config.js';
2
+ export declare const json: FlatConfig;
@@ -0,0 +1,14 @@
1
+ import eslintPluginJson from '@eslint/json';
2
+ import { defineConfig } from 'eslint/config';
3
+ // JSON files.
4
+ const jsonConfig = {
5
+ files: ['**/*.json'],
6
+ language: 'json/json',
7
+ ignores: ['package-lock.json'],
8
+ plugins: { json: eslintPluginJson },
9
+ extends: [eslintPluginJson.configs.recommended],
10
+ rules: {
11
+ 'json/top-level-interop': 'error',
12
+ },
13
+ };
14
+ export const json = defineConfig([jsonConfig]);
@@ -0,0 +1,2 @@
1
+ import type { FlatConfig } from './typescript.config.js';
2
+ export declare const lit: FlatConfig;
@@ -0,0 +1,38 @@
1
+ import eslintPluginHtml from '@html-eslint/eslint-plugin';
2
+ import { defineConfig } from 'eslint/config';
3
+ import eslintPluginHtmlScripts from 'eslint-plugin-html';
4
+ import { configs as eslintPluginLitConfigs } from 'eslint-plugin-lit';
5
+ import { configs as eslintPluginLitA11yConfigs } from 'eslint-plugin-lit-a11y';
6
+ import { configs as eslintPluginWebComponentsConfigs } from 'eslint-plugin-wc';
7
+ import { parserOptions } from './typescript.config.js';
8
+ // Lit / web-components support: element rules, a11y, and inline html-in-tagged-template linting.
9
+ const litConfig = {
10
+ files: ['**/*.js', '**/*.ts'],
11
+ extends: [
12
+ eslintPluginWebComponentsConfigs['flat/recommended'],
13
+ eslintPluginLitConfigs['flat/recommended'],
14
+ eslintPluginLitA11yConfigs.recommended,
15
+ ],
16
+ plugins: {
17
+ html: eslintPluginHtml,
18
+ htmlScripts: eslintPluginHtmlScripts,
19
+ },
20
+ languageOptions: { parserOptions },
21
+ settings: {
22
+ litHtmlSources: true,
23
+ },
24
+ rules: {
25
+ // inline html
26
+ 'html/require-img-alt': 'error',
27
+ 'html/no-multiple-h1': 'error',
28
+ 'html/no-extra-spacing-attrs': 'error',
29
+ 'html/no-duplicate-id': 'error',
30
+ 'html/require-li-container': 'error',
31
+ 'html/no-obsolete-tags': 'error',
32
+ 'html/require-closing-tags': 'error',
33
+ 'html/no-duplicate-attrs': 'error',
34
+ // web components / a11y
35
+ 'wc/guard-super-call': 'off',
36
+ },
37
+ };
38
+ export const lit = defineConfig([litConfig]);
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from './typescript.config.js';
2
+ export declare const node: FlatConfig;
3
+ export declare const nodeTypeChecked: FlatConfig;
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
3
+ import { typescript } from './typescript.config.js';
4
+ // TypeScript / JavaScript for projects that emit via tsc and run on node.
5
+ export const node = defineConfig([eslintPluginPrettierRecommended, typescript('node')]);
6
+ // Same as `node`, plus type-aware rules (needs a tsconfig; see `TypescriptOptions`).
7
+ export const nodeTypeChecked = defineConfig([
8
+ eslintPluginPrettierRecommended,
9
+ typescript('node', { typeChecked: true }),
10
+ ]);
@@ -0,0 +1,12 @@
1
+ import type { Linter } from 'eslint';
2
+ import type { Config } from 'eslint/config';
3
+ import { defineConfig } from 'eslint/config';
4
+ export type FlatConfig = ReturnType<typeof defineConfig>;
5
+ export type ConfigWithExtends = Exclude<Parameters<typeof defineConfig>[number], readonly unknown[]>;
6
+ export declare const parserOptions: Linter.ParserOptions;
7
+ export declare function setTsConfigRootDir(dir: string): Config;
8
+ export declare const importSortGroups: string[][];
9
+ export interface TypescriptOptions {
10
+ typeChecked?: boolean;
11
+ }
12
+ export declare function typescript(runtime: 'node' | 'bun', { typeChecked }?: TypescriptOptions): ConfigWithExtends;
@@ -0,0 +1,100 @@
1
+ import { fixupPluginRules } from '@eslint/compat';
2
+ import eslintPluginJs from '@eslint/js';
3
+ import eslintPluginImport from 'eslint-plugin-import';
4
+ import eslintPluginImportExtension from 'eslint-plugin-import-extensions';
5
+ import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
6
+ import eslintPluginUnusedImports from 'eslint-plugin-unused-imports';
7
+ import eslintTs from 'typescript-eslint';
8
+ // shared parser options
9
+ export const parserOptions = {
10
+ ecmaVersion: 'latest',
11
+ sourceType: 'module',
12
+ };
13
+ export function setTsConfigRootDir(dir) {
14
+ return { languageOptions: { parserOptions: { project: true, tsconfigRootDir: dir } } };
15
+ }
16
+ // shared import-sort groups
17
+ export const importSortGroups = [
18
+ // Side effect imports.
19
+ ['^\\u0000'],
20
+ // Node.js builtins prefixed with `node:`.
21
+ ['^node:'],
22
+ // Packages.
23
+ // Things that start with a letter (or digit or underscore), or `@` followed by a letter.
24
+ ['^@?\\w'],
25
+ // Absolute imports and other imports such as Vue-style `@/foo`.
26
+ // Anything not matched in another group.
27
+ ['^'],
28
+ // Relative imports.
29
+ // Anything that starts with a dot.
30
+ ['^\\.'],
31
+ // Assetic imports, most likely inline loaded style files for lit components
32
+ // or raw loaded files like images, meta data or fonts.
33
+ ['\\?(inline|raw)$'],
34
+ ];
35
+ // JavaScript / TypeScript base config, parametrised by runtime target.
36
+ //
37
+ // Relative imports always carry the emitted `.js` extension: tsc emits `.js`, and
38
+ // runtimes that execute TypeScript directly (bun, ts-node, --experimental-strip-types)
39
+ // resolve a `.js` specifier back to the neighbouring `.ts` source anyway. `runtime`
40
+ // therefore only switches the resolver, so bun builtins (`bun:test`, `bun:sqlite`) are
41
+ // recognised rather than flagged as unresolved.
42
+ export function typescript(runtime, { typeChecked = false } = {}) {
43
+ return {
44
+ files: ['**/*.js', '**/*.ts'],
45
+ extends: [
46
+ eslintPluginJs.configs.recommended,
47
+ ...(typeChecked ? eslintTs.configs.strictTypeChecked : eslintTs.configs.strict),
48
+ ...(typeChecked ? eslintTs.configs.stylisticTypeChecked : eslintTs.configs.stylistic),
49
+ eslintPluginImport.flatConfigs.recommended,
50
+ ],
51
+ plugins: {
52
+ 'simple-import-sort': eslintPluginSimpleImportSort,
53
+ 'unused-imports': eslintPluginUnusedImports,
54
+ 'import-extensions': fixupPluginRules(eslintPluginImportExtension),
55
+ },
56
+ // type-aware rules need a program; projectService (tseslint v8) auto-discovers tsconfig
57
+ languageOptions: {
58
+ parserOptions: typeChecked ? { ...parserOptions, projectService: true } : parserOptions,
59
+ },
60
+ settings: {
61
+ 'import/resolver': {
62
+ typescript: runtime === 'bun' ? { bun: true, node: true } : { node: true },
63
+ },
64
+ },
65
+ rules: {
66
+ // formatting
67
+ '@typescript-eslint/no-unused-expressions': [
68
+ 'error',
69
+ { allowShortCircuit: true, allowTernary: true },
70
+ ],
71
+ curly: 'error',
72
+ 'linebreak-style': ['error', 'unix'],
73
+ quotes: ['error', 'single', { avoidEscape: true }],
74
+ semi: ['error', 'always'],
75
+ 'no-console': 'error',
76
+ // import sorting
77
+ 'simple-import-sort/imports': ['error', { groups: importSortGroups }],
78
+ 'simple-import-sort/exports': 'error',
79
+ // import extensions
80
+ 'import-extensions/require-extensions': ['error', { expectedExtensions: ['js'] }],
81
+ 'import-extensions/require-index': ['error', { expectedExtensions: ['js'] }],
82
+ // import rules
83
+ 'import/enforce-node-protocol-usage': ['error', 'always'], // enforce node: import prefix
84
+ 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], // type imports
85
+ 'import/first': 'error',
86
+ 'import/newline-after-import': 'error',
87
+ 'import/no-duplicates': 'error',
88
+ 'import/no-unresolved': 'error',
89
+ 'import/extensions': ['error', 'ignorePackages', { ts: 'never', js: 'always' }],
90
+ 'prettier/prettier': ['error', {}, { usePrettierrc: true }],
91
+ // unused imports
92
+ '@typescript-eslint/no-unused-vars': 'off',
93
+ 'unused-imports/no-unused-vars': [
94
+ 'error',
95
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'none' },
96
+ ],
97
+ 'unused-imports/no-unused-imports': 'error',
98
+ },
99
+ };
100
+ }
@@ -1,5 +1,23 @@
1
- import type { Config } from 'eslint/config';
2
- import { defineConfig } from 'eslint/config';
3
- export declare function setTsConfigRootDir(dir: string): Config;
4
- declare const config: ReturnType<typeof defineConfig>;
1
+ import { bun, bunTypeChecked } from './configs/eslint/bun.config.js';
2
+ import { html } from './configs/eslint/html.config.js';
3
+ import { json } from './configs/eslint/json.config.js';
4
+ import { lit } from './configs/eslint/lit.config.js';
5
+ import { node, nodeTypeChecked } from './configs/eslint/node.config.js';
6
+ import type { FlatConfig } from './configs/eslint/typescript.config.js';
7
+ import { setTsConfigRootDir } from './configs/eslint/typescript.config.js';
8
+ export { bun, bunTypeChecked, html, json, lit, node, nodeTypeChecked, setTsConfigRootDir };
9
+ /**
10
+ * Legacy all-in-one configuration, kept for backwards compatibility.
11
+ *
12
+ * Bundles the `node`, `lit`, `html` and `json` chunks and additionally wires up
13
+ * gitignore-based ignores plus this package's own tsconfig root — assumptions that do not
14
+ * hold for every consumer.
15
+ *
16
+ * Prefer a ready-made preset (`@enke.dev/lint/eslint/presets/*`) or compose the individual
17
+ * single configs (`@enke.dev/lint/eslint/*`) instead.
18
+ *
19
+ * @deprecated Use a preset (`frontend`, `nodeLibrary`, `bunLibrary`) or compose the single
20
+ * configs (`node`, `bun`, `lit`, `html`, `json`).
21
+ */
22
+ declare const config: FlatConfig;
5
23
  export default config;
package/eslint.config.js CHANGED
@@ -1,21 +1,14 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { cwd } from 'node:process';
3
- import { fixupPluginRules } from '@eslint/compat';
4
- import eslintPluginJs from '@eslint/js';
5
- import eslintPluginJson from '@eslint/json';
6
- import eslintPluginHtml from '@html-eslint/eslint-plugin';
7
- import eslintParserHtml from '@html-eslint/parser';
8
3
  import { defineConfig, globalIgnores } from 'eslint/config';
9
- import eslintPluginHtmlScripts from 'eslint-plugin-html';
10
- import eslintPluginImport from 'eslint-plugin-import';
11
- import eslintPluginImportExtension from 'eslint-plugin-import-extensions';
12
- import { configs as eslintPluginLitConfigs } from 'eslint-plugin-lit';
13
- import { configs as eslintPluginLitA11yConfigs } from 'eslint-plugin-lit-a11y';
14
- import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
15
- import eslintPluginSimpleImportSort from 'eslint-plugin-simple-import-sort';
16
- import eslintPluginUnusedImports from 'eslint-plugin-unused-imports';
17
- import { configs as eslintPluginWebComponentsConfigs } from 'eslint-plugin-wc';
18
- import eslintTs from 'typescript-eslint';
4
+ import { bun, bunTypeChecked } from './configs/eslint/bun.config.js';
5
+ import { html } from './configs/eslint/html.config.js';
6
+ import { json } from './configs/eslint/json.config.js';
7
+ import { lit } from './configs/eslint/lit.config.js';
8
+ import { node, nodeTypeChecked } from './configs/eslint/node.config.js';
9
+ import { setTsConfigRootDir } from './configs/eslint/typescript.config.js';
10
+ // re-export the individual chunks so consumers can compose their own config
11
+ export { bun, bunTypeChecked, html, json, lit, node, nodeTypeChecked, setTsConfigRootDir };
19
12
  // collect gitignore excludes
20
13
  let gitIgnoreLines = [];
21
14
  const pathToGitIgnore = `${cwd()}/.gitignore`;
@@ -26,162 +19,25 @@ if (existsSync(pathToGitIgnore)) {
26
19
  .map(line => line.trim().replace(/^\//, ''))
27
20
  .filter(line => Boolean(line) && !line.startsWith('#'));
28
21
  }
29
- // shared parser options
30
- const parserOptions = {
31
- ecmaVersion: 'latest',
32
- sourceType: 'module',
33
- };
34
- export function setTsConfigRootDir(dir) {
35
- return { languageOptions: { parserOptions: { project: true, tsconfigRootDir: dir } } };
36
- }
22
+ /**
23
+ * Legacy all-in-one configuration, kept for backwards compatibility.
24
+ *
25
+ * Bundles the `node`, `lit`, `html` and `json` chunks and additionally wires up
26
+ * gitignore-based ignores plus this package's own tsconfig root — assumptions that do not
27
+ * hold for every consumer.
28
+ *
29
+ * Prefer a ready-made preset (`@enke.dev/lint/eslint/presets/*`) or compose the individual
30
+ * single configs (`@enke.dev/lint/eslint/*`) instead.
31
+ *
32
+ * @deprecated Use a preset (`frontend`, `nodeLibrary`, `bunLibrary`) or compose the single
33
+ * configs (`node`, `bun`, `lit`, `html`, `json`).
34
+ */
37
35
  const config = defineConfig([
38
36
  globalIgnores([...gitIgnoreLines, 'dist/']),
39
- eslintPluginPrettierRecommended,
40
37
  setTsConfigRootDir(import.meta.dirname),
41
- // Javascript and Typescript files
42
- {
43
- files: ['**/*.js', '**/*.ts'],
44
- extends: [
45
- eslintPluginJs.configs.recommended,
46
- ...eslintTs.configs.strict,
47
- ...eslintTs.configs.stylistic,
48
- eslintPluginWebComponentsConfigs['flat/recommended'],
49
- eslintPluginLitConfigs['flat/recommended'],
50
- eslintPluginLitA11yConfigs.recommended,
51
- eslintPluginImport.flatConfigs.recommended,
52
- ],
53
- plugins: {
54
- 'simple-import-sort': eslintPluginSimpleImportSort,
55
- 'unused-imports': eslintPluginUnusedImports,
56
- 'import-extensions': fixupPluginRules(eslintPluginImportExtension),
57
- html: eslintPluginHtml,
58
- htmlScripts: eslintPluginHtmlScripts,
59
- },
60
- languageOptions: { parserOptions },
61
- settings: {
62
- 'import/resolver': {
63
- typescript: {
64
- bun: true,
65
- node: true,
66
- },
67
- },
68
- litHtmlSources: true,
69
- },
70
- rules: {
71
- // formatting
72
- '@typescript-eslint/no-unused-expressions': [
73
- 'error',
74
- { allowShortCircuit: true, allowTernary: true },
75
- ],
76
- curly: 'error',
77
- 'linebreak-style': ['error', 'unix'],
78
- quotes: ['error', 'single', { avoidEscape: true }],
79
- semi: ['error', 'always'],
80
- 'no-console': 'error',
81
- // inline html
82
- 'html/require-img-alt': 'error',
83
- 'html/no-multiple-h1': 'error',
84
- 'html/no-extra-spacing-attrs': 'error',
85
- 'html/no-duplicate-id': 'error',
86
- 'html/require-li-container': 'error',
87
- 'html/no-obsolete-tags': 'error',
88
- 'html/require-closing-tags': 'error',
89
- 'html/no-duplicate-attrs': 'error',
90
- // import sorting
91
- 'simple-import-sort/imports': [
92
- 'error',
93
- {
94
- groups: [
95
- // Side effect imports.
96
- ['^\\u0000'],
97
- // Node.js builtins prefixed with `node:`.
98
- ['^node:'],
99
- // Packages.
100
- // Things that start with a letter (or digit or underscore), or `@` followed by a letter.
101
- ['^@?\\w'],
102
- // Absolute imports and other imports such as Vue-style `@/foo`.
103
- // Anything not matched in another group.
104
- ['^'],
105
- // Relative imports.
106
- // Anything that starts with a dot.
107
- ['^\\.'],
108
- // Assetic imports, most likely inline loaded style files for lit components
109
- // or raw loaded files like images, meta data or fonts.
110
- ['\\?(inline|raw)$'],
111
- ],
112
- },
113
- ],
114
- 'simple-import-sort/exports': 'error',
115
- // import extensions
116
- 'import-extensions/require-extensions': ['error', { expectedExtensions: ['js'] }],
117
- 'import-extensions/require-index': ['error', { expectedExtensions: ['js'] }],
118
- // import rules
119
- 'import/enforce-node-protocol-usage': ['error', 'always'], // enforce node: import prefix
120
- 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], // type imports
121
- 'import/first': 'error',
122
- 'import/newline-after-import': 'error',
123
- 'import/no-duplicates': 'error',
124
- 'import/no-unresolved': 'error',
125
- 'import/extensions': ['error', 'ignorePackages', { ts: 'never', js: 'always' }],
126
- 'prettier/prettier': ['error', {}, { usePrettierrc: true }],
127
- // unused imports
128
- '@typescript-eslint/no-unused-vars': 'off',
129
- 'unused-imports/no-unused-vars': [
130
- 'error',
131
- { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'none' },
132
- ],
133
- 'unused-imports/no-unused-imports': 'error',
134
- // web components / a11y
135
- 'wc/guard-super-call': 'off',
136
- },
137
- },
138
- // HTML files
139
- {
140
- files: ['**/*.html'],
141
- plugins: {
142
- html: eslintPluginHtml,
143
- htmlScripts: eslintPluginHtmlScripts,
144
- },
145
- extends: ['html/recommended'],
146
- language: 'html/html',
147
- languageOptions: {
148
- parser: eslintParserHtml,
149
- parserOptions,
150
- },
151
- settings: {
152
- 'html/indent': '+2',
153
- 'html/lowercase': 'error',
154
- 'html/no-accesskey-attrs': 'error',
155
- 'html/no-aria-hidden-body': 'error',
156
- 'html/no-aria-hidden-on-focusable': 'error',
157
- 'html/no-duplicate-class': 'error',
158
- 'html/no-duplicate-in-head': 'error',
159
- 'html/no-empty-headings': 'error',
160
- 'html/no-invalid-entities': 'error',
161
- 'html/no-heading-inside-button': 'error',
162
- 'html/no-invalid-entity': 'error',
163
- 'html/no-invalid-role': 'error',
164
- 'html/no-nested-interactive': 'error',
165
- 'html/no-multiple-empty-lines': 'error',
166
- 'html/no-target-blank': 'error',
167
- 'html/no-trailing-spaces': 'error',
168
- 'html/report-bad-indent': 'error',
169
- 'html/require-input-label': 'error',
170
- },
171
- rules: {
172
- 'html/indent': ['error', 2],
173
- },
174
- },
175
- // JSON files
176
- {
177
- files: ['**/*.json'],
178
- language: 'json/json',
179
- ignores: ['package-lock.json'],
180
- plugins: { json: eslintPluginJson },
181
- extends: [eslintPluginJson.configs.recommended],
182
- rules: {
183
- 'json/top-level-interop': 'error',
184
- },
185
- },
38
+ ...node,
39
+ ...lit,
40
+ ...html,
41
+ ...json,
186
42
  ]);
187
43
  export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enke.dev/lint",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Meta package to provide linting for web projects",
5
5
  "homepage": "https://github.com/enke-dev/lint",
6
6
  "repository": {
@@ -8,22 +8,62 @@
8
8
  "url": "git+https://github.com/enke-dev/lint.git"
9
9
  },
10
10
  "files": [
11
- "eslint.config.d.ts",
12
- "eslint.config.js",
13
- "eslint-plugins.d.ts",
14
- "prettier.config.d.ts",
15
- "prettier.config.js",
16
- "stylelint.config.d.ts",
17
- "stylelint.config.js",
11
+ "*.config.js",
12
+ "*.config.d.ts",
13
+ "configs/eslint/*.js",
14
+ "configs/eslint/*.d.ts",
15
+ "presets/eslint/*.js",
16
+ "presets/eslint/*.d.ts",
18
17
  "README.md"
19
18
  ],
20
19
  "type": "module",
21
20
  "main": "eslint.config.js",
22
21
  "types": "eslint.config.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./eslint.config.d.ts",
25
+ "default": "./eslint.config.js"
26
+ },
27
+ "./eslint/node": {
28
+ "types": "./configs/eslint/node.config.d.ts",
29
+ "default": "./configs/eslint/node.config.js"
30
+ },
31
+ "./eslint/bun": {
32
+ "types": "./configs/eslint/bun.config.d.ts",
33
+ "default": "./configs/eslint/bun.config.js"
34
+ },
35
+ "./eslint/lit": {
36
+ "types": "./configs/eslint/lit.config.d.ts",
37
+ "default": "./configs/eslint/lit.config.js"
38
+ },
39
+ "./eslint/html": {
40
+ "types": "./configs/eslint/html.config.d.ts",
41
+ "default": "./configs/eslint/html.config.js"
42
+ },
43
+ "./eslint/json": {
44
+ "types": "./configs/eslint/json.config.d.ts",
45
+ "default": "./configs/eslint/json.config.js"
46
+ },
47
+ "./eslint/presets/frontend": {
48
+ "types": "./presets/eslint/frontend.preset.d.ts",
49
+ "default": "./presets/eslint/frontend.preset.js"
50
+ },
51
+ "./eslint/presets/node-library": {
52
+ "types": "./presets/eslint/node-library.preset.d.ts",
53
+ "default": "./presets/eslint/node-library.preset.js"
54
+ },
55
+ "./eslint/presets/bun-library": {
56
+ "types": "./presets/eslint/bun-library.preset.d.ts",
57
+ "default": "./presets/eslint/bun-library.preset.js"
58
+ },
59
+ "./*": "./*"
60
+ },
23
61
  "scripts": {
24
62
  "check": "tsc -p tsconfig.build.json --noEmit",
25
63
  "lint": "eslint -c test.eslint.config.ts .",
26
- "test": "node --experimental-strip-types --test test.run.ts",
64
+ "test": "run-p -ns test:*",
65
+ "test:node": "node --experimental-strip-types --test test.run.node.ts",
66
+ "test:bun": "bun test ./test.run.bun.ts",
27
67
  "inspect": "eslint-config-inspector --config eslint.config.ts",
28
68
  "dev": "tsc -p tsconfig.build.json --watch",
29
69
  "build": "tsc -p tsconfig.build.json"
@@ -42,8 +82,8 @@
42
82
  },
43
83
  "peerDependencies": {
44
84
  "@awmottaz/prettier-plugin-void-html": "^2.1.0",
45
- "eslint": "^10.6.0",
46
- "prettier": "^3.9.4"
85
+ "eslint": "^10.7.0",
86
+ "prettier": "^3.9.5"
47
87
  },
48
88
  "optionalDependencies": {
49
89
  "jiti": "^2.7.0",
@@ -52,14 +92,14 @@
52
92
  "stylelint-config-standard-scss": "^17.0.0",
53
93
  "stylelint-order": "^8.1.1",
54
94
  "typescript": "^6.0.3",
55
- "typescript-eslint": "^8.62.1"
95
+ "typescript-eslint": "^8.63.0"
56
96
  },
57
97
  "dependencies": {
58
98
  "@eslint/compat": "2.1.0",
59
99
  "@eslint/js": "10.0.1",
60
100
  "@eslint/json": "2.0.1",
61
- "@html-eslint/eslint-plugin": "0.63.0",
62
- "@html-eslint/parser": "0.63.0",
101
+ "@html-eslint/eslint-plugin": "0.64.0",
102
+ "@html-eslint/parser": "0.64.0",
63
103
  "eslint-config-prettier": "10.1.8",
64
104
  "eslint-import-resolver-typescript": "4.4.5",
65
105
  "eslint-plugin-html": "8.1.4",
@@ -76,15 +116,16 @@
76
116
  "devDependencies": {
77
117
  "@awmottaz/prettier-plugin-void-html": "2.1.0",
78
118
  "@eslint/config-inspector": "3.0.4",
79
- "@types/node": "24.13.2",
80
- "eslint": "10.6.0",
119
+ "@types/node": "24.13.3",
120
+ "eslint": "10.7.0",
81
121
  "jiti": "2.7.0",
82
- "prettier": "3.9.4",
122
+ "npm-run-all2": "9.0.2",
123
+ "prettier": "3.9.5",
83
124
  "stylelint": "17.14.0",
84
125
  "stylelint-config-rational-order": "0.1.2",
85
126
  "stylelint-config-standard-scss": "17.0.0",
86
127
  "stylelint-order": "8.1.1",
87
128
  "typescript": "6.0.3",
88
- "typescript-eslint": "8.62.1"
129
+ "typescript-eslint": "8.63.0"
89
130
  }
90
131
  }
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from '../../configs/eslint/typescript.config.js';
2
+ export declare const bunLibrary: FlatConfig;
3
+ export declare const bunLibraryTypeChecked: FlatConfig;
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import { bun, bunTypeChecked } from '../../configs/eslint/bun.config.js';
3
+ import { json } from '../../configs/eslint/json.config.js';
4
+ // Bun library: same as the node library but with the bun module resolver so builtins
5
+ // such as `bun:test` and `bun:sqlite` resolve instead of being flagged as unresolved.
6
+ export const bunLibrary = defineConfig([...bun, ...json]);
7
+ // Same as `bunLibrary`, plus type-aware rules (needs a tsconfig; see `TypescriptOptions`).
8
+ export const bunLibraryTypeChecked = defineConfig([...bunTypeChecked, ...json]);
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from '../../configs/eslint/typescript.config.js';
2
+ export declare const frontend: FlatConfig;
3
+ export declare const frontendTypeChecked: FlatConfig;
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import { html } from '../../configs/eslint/html.config.js';
3
+ import { json } from '../../configs/eslint/json.config.js';
4
+ import { lit } from '../../configs/eslint/lit.config.js';
5
+ import { node, nodeTypeChecked } from '../../configs/eslint/node.config.js';
6
+ // Frontend projects: TypeScript/JavaScript with web components (lit), inline and
7
+ // standalone HTML, and JSON. Bundled/emitted via tsc, so node-style `.js` import
8
+ // extensions and the node resolver.
9
+ export const frontend = defineConfig([...node, ...lit, ...html, ...json]);
10
+ // Same as `frontend`, plus type-aware rules (needs a tsconfig; see `TypescriptOptions`).
11
+ export const frontendTypeChecked = defineConfig([
12
+ ...nodeTypeChecked,
13
+ ...lit,
14
+ ...html,
15
+ ...json,
16
+ ]);
@@ -0,0 +1,3 @@
1
+ import type { FlatConfig } from '../../configs/eslint/typescript.config.js';
2
+ export declare const nodeLibrary: FlatConfig;
3
+ export declare const nodeLibraryTypeChecked: FlatConfig;
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import { json } from '../../configs/eslint/json.config.js';
3
+ import { node, nodeTypeChecked } from '../../configs/eslint/node.config.js';
4
+ // Node library: TypeScript/JavaScript emitted via tsc plus JSON. No web components or
5
+ // standalone HTML.
6
+ export const nodeLibrary = defineConfig([...node, ...json]);
7
+ // Same as `nodeLibrary`, plus type-aware rules (needs a tsconfig; see `TypescriptOptions`).
8
+ export const nodeLibraryTypeChecked = defineConfig([...nodeTypeChecked, ...json]);