mirror of
https://github.com/robonen/tools.git
synced 2026-03-20 02:44:45 +00:00
feat(configs/oxlint): add linter
This commit is contained in:
54
configs/oxlint/README.md
Normal file
54
configs/oxlint/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# @robonen/oxlint
|
||||
|
||||
Composable [oxlint](https://oxc.rs/docs/guide/usage/linter.html) configuration presets.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pnpm install -D @robonen/oxlint oxlint
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Create `oxlint.config.ts` in your project root:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from 'oxlint';
|
||||
import { compose, base, typescript, vue, vitest, imports } from '@robonen/oxlint';
|
||||
|
||||
export default defineConfig(
|
||||
compose(base, typescript, vue, vitest, imports),
|
||||
);
|
||||
```
|
||||
|
||||
Append custom rules after presets to override them:
|
||||
|
||||
```ts
|
||||
compose(base, typescript, {
|
||||
rules: { 'eslint/no-console': 'off' },
|
||||
ignorePatterns: ['dist'],
|
||||
});
|
||||
```
|
||||
|
||||
## Presets
|
||||
|
||||
| Preset | Description |
|
||||
| ------------ | -------------------------------------------------- |
|
||||
| `base` | Core eslint, oxc, unicorn rules |
|
||||
| `typescript` | TypeScript-specific rules (via overrides) |
|
||||
| `vue` | Vue 3 Composition API / `<script setup>` rules |
|
||||
| `vitest` | Test file rules (via overrides) |
|
||||
| `imports` | Import rules (cycles, duplicates, ordering) |
|
||||
| `node` | Node.js-specific rules |
|
||||
|
||||
## API
|
||||
|
||||
### `compose(...configs: OxlintConfig[]): OxlintConfig`
|
||||
|
||||
Merges multiple configs into one:
|
||||
|
||||
- **plugins** — union (deduplicated)
|
||||
- **rules / categories** — last wins
|
||||
- **overrides / ignorePatterns** — concatenated
|
||||
- **env / globals** — shallow merge
|
||||
- **settings** — deep merge
|
||||
4
configs/oxlint/oxlint.config.ts
Normal file
4
configs/oxlint/oxlint.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { defineConfig } from 'oxlint';
|
||||
import { compose, base, typescript, imports } from '@robonen/oxlint';
|
||||
|
||||
export default defineConfig(compose(base, typescript, imports));
|
||||
52
configs/oxlint/package.json
Normal file
52
configs/oxlint/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@robonen/oxlint",
|
||||
"version": "0.0.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Composable oxlint configuration presets",
|
||||
"keywords": [
|
||||
"oxlint",
|
||||
"oxc",
|
||||
"linter",
|
||||
"config",
|
||||
"presets"
|
||||
],
|
||||
"author": "Robonen Andrew <robonenandrew@gmail.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/robonen/tools.git",
|
||||
"directory": "configs/oxlint"
|
||||
},
|
||||
"packageManager": "pnpm@10.29.3",
|
||||
"engines": {
|
||||
"node": ">=22.18.0"
|
||||
},
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "oxlint -c oxlint.config.ts",
|
||||
"test": "vitest run",
|
||||
"dev": "vitest dev",
|
||||
"build": "tsdown"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@robonen/oxlint": "workspace:*",
|
||||
"@robonen/tsconfig": "workspace:*",
|
||||
"oxlint": "catalog:",
|
||||
"tsdown": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"oxlint": ">=1.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
103
configs/oxlint/src/compose.ts
Normal file
103
configs/oxlint/src/compose.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { OxlintConfig } from './types';
|
||||
|
||||
/**
|
||||
* Deep merge two objects. Arrays are concatenated, objects are recursively merged.
|
||||
*/
|
||||
function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown> {
|
||||
const result = { ...target };
|
||||
|
||||
for (const key of Object.keys(source)) {
|
||||
const targetValue = target[key];
|
||||
const sourceValue = source[key];
|
||||
|
||||
if (
|
||||
typeof targetValue === 'object' && targetValue !== null && !Array.isArray(targetValue)
|
||||
&& typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)
|
||||
) {
|
||||
result[key] = deepMerge(
|
||||
targetValue as Record<string, unknown>,
|
||||
sourceValue as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
else {
|
||||
result[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose multiple oxlint configurations into a single config.
|
||||
*
|
||||
* - `plugins` — union (deduplicated)
|
||||
* - `categories` — later configs override earlier
|
||||
* - `rules` — later configs override earlier
|
||||
* - `overrides` — concatenated
|
||||
* - `env` — merged (later overrides earlier)
|
||||
* - `globals` — merged (later overrides earlier)
|
||||
* - `settings` — deep-merged
|
||||
* - `ignorePatterns` — concatenated
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { compose, base, typescript, vue } from '@robonen/oxlint';
|
||||
* import { defineConfig } from 'oxlint';
|
||||
*
|
||||
* export default defineConfig(
|
||||
* compose(base, typescript, vue, {
|
||||
* rules: { 'eslint/no-console': 'off' },
|
||||
* }),
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function compose(...configs: OxlintConfig[]): OxlintConfig {
|
||||
const result: OxlintConfig = {};
|
||||
|
||||
for (const config of configs) {
|
||||
// Plugins — union with dedup
|
||||
if (config.plugins?.length) {
|
||||
result.plugins = Array.from(new Set([...(result.plugins ?? []), ...config.plugins]));
|
||||
}
|
||||
|
||||
// Categories — shallow merge
|
||||
if (config.categories) {
|
||||
result.categories = { ...result.categories, ...config.categories };
|
||||
}
|
||||
|
||||
// Rules — shallow merge (later overrides earlier)
|
||||
if (config.rules) {
|
||||
result.rules = { ...result.rules, ...config.rules };
|
||||
}
|
||||
|
||||
// Overrides — concatenate
|
||||
if (config.overrides?.length) {
|
||||
result.overrides = [...(result.overrides ?? []), ...config.overrides];
|
||||
}
|
||||
|
||||
// Env — shallow merge
|
||||
if (config.env) {
|
||||
result.env = { ...result.env, ...config.env };
|
||||
}
|
||||
|
||||
// Globals — shallow merge
|
||||
if (config.globals) {
|
||||
result.globals = { ...result.globals, ...config.globals };
|
||||
}
|
||||
|
||||
// Settings — deep merge
|
||||
if (config.settings) {
|
||||
result.settings = deepMerge(
|
||||
(result.settings ?? {}) as Record<string, unknown>,
|
||||
config.settings as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
|
||||
// Ignore patterns — concatenate
|
||||
if (config.ignorePatterns?.length) {
|
||||
result.ignorePatterns = [...(result.ignorePatterns ?? []), ...config.ignorePatterns];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
17
configs/oxlint/src/index.ts
Normal file
17
configs/oxlint/src/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/* Compose */
|
||||
export { compose } from './compose';
|
||||
|
||||
/* Presets */
|
||||
export { base, typescript, vue, vitest, imports, node } from './presets';
|
||||
|
||||
/* Types */
|
||||
export type {
|
||||
OxlintConfig,
|
||||
OxlintOverride,
|
||||
OxlintEnv,
|
||||
OxlintGlobals,
|
||||
AllowWarnDeny,
|
||||
DummyRule,
|
||||
DummyRuleMap,
|
||||
RuleCategories,
|
||||
} from './types';
|
||||
73
configs/oxlint/src/presets/base.ts
Normal file
73
configs/oxlint/src/presets/base.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Base configuration for any JavaScript/TypeScript project.
|
||||
*
|
||||
* Enables `correctness` category and opinionated rules from
|
||||
* `eslint`, `oxc`, and `unicorn` plugins.
|
||||
*/
|
||||
export const base: OxlintConfig = {
|
||||
plugins: ['eslint', 'oxc', 'unicorn'],
|
||||
|
||||
categories: {
|
||||
correctness: 'error',
|
||||
},
|
||||
|
||||
rules: {
|
||||
/* ── eslint core ──────────────────────────────────────── */
|
||||
'eslint/eqeqeq': 'error',
|
||||
'eslint/no-console': 'warn',
|
||||
'eslint/no-debugger': 'error',
|
||||
'eslint/no-eval': 'error',
|
||||
'eslint/no-var': 'error',
|
||||
'eslint/prefer-const': 'error',
|
||||
'eslint/prefer-template': 'warn',
|
||||
'eslint/no-useless-constructor': 'warn',
|
||||
'eslint/no-useless-rename': 'warn',
|
||||
'eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
||||
'eslint/no-self-compare': 'error',
|
||||
'eslint/no-template-curly-in-string': 'warn',
|
||||
'eslint/no-throw-literal': 'error',
|
||||
'eslint/no-return-assign': 'warn',
|
||||
'eslint/no-else-return': 'warn',
|
||||
'eslint/no-lonely-if': 'warn',
|
||||
'eslint/no-unneeded-ternary': 'warn',
|
||||
'eslint/prefer-object-spread': 'warn',
|
||||
'eslint/prefer-exponentiation-operator': 'warn',
|
||||
'eslint/no-useless-computed-key': 'warn',
|
||||
'eslint/no-useless-concat': 'warn',
|
||||
'eslint/curly': 'off',
|
||||
|
||||
/* ── unicorn ──────────────────────────────────────────── */
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
'unicorn/no-instanceof-array': 'error',
|
||||
'unicorn/no-new-array': 'error',
|
||||
'unicorn/prefer-array-flat-map': 'warn',
|
||||
'unicorn/prefer-array-flat': 'warn',
|
||||
'unicorn/prefer-includes': 'warn',
|
||||
'unicorn/prefer-string-slice': 'warn',
|
||||
'unicorn/prefer-string-starts-ends-with': 'warn',
|
||||
'unicorn/throw-new-error': 'error',
|
||||
'unicorn/error-message': 'warn',
|
||||
'unicorn/no-useless-spread': 'warn',
|
||||
'unicorn/no-useless-undefined': 'off',
|
||||
'unicorn/prefer-optional-catch-binding': 'warn',
|
||||
'unicorn/prefer-type-error': 'warn',
|
||||
'unicorn/no-thenable': 'error',
|
||||
'unicorn/prefer-number-properties': 'warn',
|
||||
'unicorn/prefer-global-this': 'warn',
|
||||
|
||||
/* ── oxc ──────────────────────────────────────────────── */
|
||||
'oxc/no-accumulating-spread': 'warn',
|
||||
'oxc/bad-comparison-sequence': 'error',
|
||||
'oxc/bad-min-max-func': 'error',
|
||||
'oxc/bad-object-literal-comparison': 'error',
|
||||
'oxc/const-comparisons': 'error',
|
||||
'oxc/double-comparisons': 'error',
|
||||
'oxc/erasing-op': 'error',
|
||||
'oxc/missing-throw': 'error',
|
||||
'oxc/bad-bitwise-operator': 'error',
|
||||
'oxc/bad-char-at-comparison': 'error',
|
||||
'oxc/bad-replace-all-arg': 'error',
|
||||
},
|
||||
};
|
||||
20
configs/oxlint/src/presets/imports.ts
Normal file
20
configs/oxlint/src/presets/imports.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Import plugin rules for clean module boundaries.
|
||||
*/
|
||||
export const imports: OxlintConfig = {
|
||||
plugins: ['import'],
|
||||
|
||||
rules: {
|
||||
'import/no-duplicates': 'error',
|
||||
'import/no-self-import': 'error',
|
||||
'import/no-cycle': 'warn',
|
||||
'import/first': 'warn',
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-amd': 'error',
|
||||
'import/no-commonjs': 'warn',
|
||||
'import/no-empty-named-blocks': 'warn',
|
||||
'import/consistent-type-specifier-style': ['warn', 'prefer-top-level'],
|
||||
},
|
||||
};
|
||||
6
configs/oxlint/src/presets/index.ts
Normal file
6
configs/oxlint/src/presets/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { base } from './base';
|
||||
export { typescript } from './typescript';
|
||||
export { vue } from './vue';
|
||||
export { vitest } from './vitest';
|
||||
export { imports } from './imports';
|
||||
export { node } from './node';
|
||||
17
configs/oxlint/src/presets/node.ts
Normal file
17
configs/oxlint/src/presets/node.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Node.js-specific rules.
|
||||
*/
|
||||
export const node: OxlintConfig = {
|
||||
plugins: ['node'],
|
||||
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
|
||||
rules: {
|
||||
'node/no-exports-assign': 'error',
|
||||
'node/no-new-require': 'error',
|
||||
},
|
||||
};
|
||||
39
configs/oxlint/src/presets/typescript.ts
Normal file
39
configs/oxlint/src/presets/typescript.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* TypeScript-specific rules.
|
||||
*
|
||||
* Applied via `overrides` for `*.ts`, `*.tsx`, `*.mts`, `*.cts` files.
|
||||
*/
|
||||
export const typescript: OxlintConfig = {
|
||||
plugins: ['typescript'],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||
rules: {
|
||||
'typescript/consistent-type-imports': 'error',
|
||||
'typescript/no-explicit-any': 'off',
|
||||
'typescript/no-non-null-assertion': 'off',
|
||||
'typescript/prefer-as-const': 'error',
|
||||
'typescript/no-empty-object-type': 'warn',
|
||||
'typescript/no-wrapper-object-types': 'error',
|
||||
'typescript/no-duplicate-enum-values': 'error',
|
||||
'typescript/no-unsafe-declaration-merging': 'error',
|
||||
'typescript/no-import-type-side-effects': 'error',
|
||||
'typescript/no-useless-empty-export': 'warn',
|
||||
'typescript/no-inferrable-types': 'warn',
|
||||
'typescript/prefer-function-type': 'warn',
|
||||
'typescript/ban-tslint-comment': 'error',
|
||||
'typescript/consistent-type-definitions': ['warn', 'interface'],
|
||||
'typescript/prefer-for-of': 'warn',
|
||||
'typescript/no-unnecessary-type-constraint': 'warn',
|
||||
'typescript/adjacent-overload-signatures': 'warn',
|
||||
'typescript/array-type': ['warn', { default: 'array-simple' }],
|
||||
'typescript/no-this-alias': 'error',
|
||||
'typescript/triple-slash-reference': 'error',
|
||||
'typescript/no-namespace': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
35
configs/oxlint/src/presets/vitest.ts
Normal file
35
configs/oxlint/src/presets/vitest.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Vitest rules for test files.
|
||||
*
|
||||
* Applied via `overrides` for common test file patterns.
|
||||
*/
|
||||
export const vitest: OxlintConfig = {
|
||||
plugins: ['vitest'],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/*.test.{ts,tsx,js,jsx}',
|
||||
'**/*.spec.{ts,tsx,js,jsx}',
|
||||
'**/test/**/*.{ts,tsx,js,jsx}',
|
||||
'**/__tests__/**/*.{ts,tsx,js,jsx}',
|
||||
],
|
||||
rules: {
|
||||
'vitest/no-conditional-tests': 'warn',
|
||||
'vitest/no-import-node-test': 'error',
|
||||
'vitest/prefer-to-be-truthy': 'warn',
|
||||
'vitest/prefer-to-be-falsy': 'warn',
|
||||
'vitest/prefer-to-be-object': 'warn',
|
||||
'vitest/prefer-to-have-length': 'warn',
|
||||
'vitest/consistent-test-filename': 'warn',
|
||||
'vitest/prefer-describe-function-title': 'warn',
|
||||
|
||||
/* relax strict rules in tests */
|
||||
'eslint/no-unused-vars': 'off',
|
||||
'typescript/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
26
configs/oxlint/src/presets/vue.ts
Normal file
26
configs/oxlint/src/presets/vue.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { OxlintConfig } from '../types';
|
||||
|
||||
/**
|
||||
* Vue.js-specific rules.
|
||||
*
|
||||
* Enforces Composition API with `<script setup>` and type-based declarations.
|
||||
*/
|
||||
export const vue: OxlintConfig = {
|
||||
plugins: ['vue'],
|
||||
|
||||
rules: {
|
||||
'vue/no-arrow-functions-in-watch': 'error',
|
||||
'vue/no-deprecated-destroyed-lifecycle': 'error',
|
||||
'vue/no-export-in-script-setup': 'error',
|
||||
'vue/no-lifecycle-after-await': 'error',
|
||||
'vue/no-multiple-slot-args': 'error',
|
||||
'vue/no-import-compiler-macros': 'error',
|
||||
'vue/define-emits-declaration': ['error', 'type-based'],
|
||||
'vue/define-props-declaration': ['error', 'type-based'],
|
||||
'vue/prefer-import-from-vue': 'error',
|
||||
'vue/no-required-prop-with-default': 'warn',
|
||||
'vue/valid-define-emits': 'error',
|
||||
'vue/valid-define-props': 'error',
|
||||
'vue/require-typed-ref': 'warn',
|
||||
},
|
||||
};
|
||||
18
configs/oxlint/src/types.ts
Normal file
18
configs/oxlint/src/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Re-exported configuration types from `oxlint`.
|
||||
*
|
||||
* Keeps the preset API in sync with the oxlint CLI without
|
||||
* maintaining a separate copy of the types.
|
||||
*
|
||||
* @see https://oxc.rs/docs/guide/usage/linter/config-file-reference.html
|
||||
*/
|
||||
export type {
|
||||
OxlintConfig,
|
||||
OxlintOverride,
|
||||
OxlintEnv,
|
||||
OxlintGlobals,
|
||||
AllowWarnDeny,
|
||||
DummyRule,
|
||||
DummyRuleMap,
|
||||
RuleCategories,
|
||||
} from 'oxlint';
|
||||
146
configs/oxlint/test/compose.test.ts
Normal file
146
configs/oxlint/test/compose.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compose } from '../src/compose';
|
||||
import type { OxlintConfig } from '../src/types';
|
||||
|
||||
describe('compose', () => {
|
||||
it('should return empty config when no configs provided', () => {
|
||||
expect(compose()).toEqual({});
|
||||
});
|
||||
|
||||
it('should return the same config when one config provided', () => {
|
||||
const config: OxlintConfig = {
|
||||
plugins: ['eslint'],
|
||||
rules: { 'eslint/no-console': 'warn' },
|
||||
};
|
||||
const result = compose(config);
|
||||
expect(result.plugins).toEqual(['eslint']);
|
||||
expect(result.rules).toEqual({ 'eslint/no-console': 'warn' });
|
||||
});
|
||||
|
||||
it('should merge plugins with dedup', () => {
|
||||
const a: OxlintConfig = { plugins: ['eslint', 'oxc'] };
|
||||
const b: OxlintConfig = { plugins: ['oxc', 'typescript'] };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.plugins).toEqual(['eslint', 'oxc', 'typescript']);
|
||||
});
|
||||
|
||||
it('should override rules from later configs', () => {
|
||||
const a: OxlintConfig = { rules: { 'eslint/no-console': 'error', 'eslint/eqeqeq': 'warn' } };
|
||||
const b: OxlintConfig = { rules: { 'eslint/no-console': 'off' } };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.rules).toEqual({
|
||||
'eslint/no-console': 'off',
|
||||
'eslint/eqeqeq': 'warn',
|
||||
});
|
||||
});
|
||||
|
||||
it('should override categories from later configs', () => {
|
||||
const a: OxlintConfig = { categories: { correctness: 'error', suspicious: 'warn' } };
|
||||
const b: OxlintConfig = { categories: { suspicious: 'off' } };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.categories).toEqual({
|
||||
correctness: 'error',
|
||||
suspicious: 'off',
|
||||
});
|
||||
});
|
||||
|
||||
it('should concatenate overrides', () => {
|
||||
const a: OxlintConfig = {
|
||||
overrides: [{ files: ['**/*.ts'], rules: { 'typescript/no-explicit-any': 'warn' } }],
|
||||
};
|
||||
const b: OxlintConfig = {
|
||||
overrides: [{ files: ['**/*.test.ts'], rules: { 'eslint/no-unused-vars': 'off' } }],
|
||||
};
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.overrides).toHaveLength(2);
|
||||
expect(result.overrides?.[0]?.files).toEqual(['**/*.ts']);
|
||||
expect(result.overrides?.[1]?.files).toEqual(['**/*.test.ts']);
|
||||
});
|
||||
|
||||
it('should merge env', () => {
|
||||
const a: OxlintConfig = { env: { browser: true } };
|
||||
const b: OxlintConfig = { env: { node: true } };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.env).toEqual({ browser: true, node: true });
|
||||
});
|
||||
|
||||
it('should merge globals', () => {
|
||||
const a: OxlintConfig = { globals: { MY_VAR: 'readonly' } };
|
||||
const b: OxlintConfig = { globals: { ANOTHER: 'writable' } };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.globals).toEqual({ MY_VAR: 'readonly', ANOTHER: 'writable' });
|
||||
});
|
||||
|
||||
it('should deep merge settings', () => {
|
||||
const a: OxlintConfig = {
|
||||
settings: {
|
||||
react: { version: '18.2.0' },
|
||||
next: { rootDir: 'apps/' },
|
||||
},
|
||||
};
|
||||
const b: OxlintConfig = {
|
||||
settings: {
|
||||
react: { linkComponents: [{ name: 'Link', linkAttribute: 'to', attributes: ['to'] }] },
|
||||
},
|
||||
};
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.settings).toEqual({
|
||||
react: {
|
||||
version: '18.2.0',
|
||||
linkComponents: [{ name: 'Link', linkAttribute: 'to', attributes: ['to'] }],
|
||||
},
|
||||
next: { rootDir: 'apps/' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should concatenate ignorePatterns', () => {
|
||||
const a: OxlintConfig = { ignorePatterns: ['dist'] };
|
||||
const b: OxlintConfig = { ignorePatterns: ['node_modules', 'coverage'] };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.ignorePatterns).toEqual(['dist', 'node_modules', 'coverage']);
|
||||
});
|
||||
|
||||
it('should handle composing all presets together', () => {
|
||||
const base: OxlintConfig = {
|
||||
plugins: ['eslint', 'oxc'],
|
||||
categories: { correctness: 'error' },
|
||||
rules: { 'eslint/no-console': 'warn' },
|
||||
};
|
||||
const ts: OxlintConfig = {
|
||||
plugins: ['typescript'],
|
||||
overrides: [{ files: ['**/*.ts'], rules: { 'typescript/no-explicit-any': 'warn' } }],
|
||||
};
|
||||
const custom: OxlintConfig = {
|
||||
rules: { 'eslint/no-console': 'off' },
|
||||
ignorePatterns: ['dist'],
|
||||
};
|
||||
|
||||
const result = compose(base, ts, custom);
|
||||
|
||||
expect(result.plugins).toEqual(['eslint', 'oxc', 'typescript']);
|
||||
expect(result.categories).toEqual({ correctness: 'error' });
|
||||
expect(result.rules).toEqual({ 'eslint/no-console': 'off' });
|
||||
expect(result.overrides).toHaveLength(1);
|
||||
expect(result.ignorePatterns).toEqual(['dist']);
|
||||
});
|
||||
|
||||
it('should skip undefined/empty fields', () => {
|
||||
const a: OxlintConfig = { plugins: ['eslint'] };
|
||||
const b: OxlintConfig = { rules: { 'eslint/no-console': 'warn' } };
|
||||
|
||||
const result = compose(a, b);
|
||||
expect(result.plugins).toEqual(['eslint']);
|
||||
expect(result.rules).toEqual({ 'eslint/no-console': 'warn' });
|
||||
expect(result.overrides).toBeUndefined();
|
||||
expect(result.env).toBeUndefined();
|
||||
expect(result.settings).toBeUndefined();
|
||||
});
|
||||
});
|
||||
6
configs/oxlint/tsconfig.json
Normal file
6
configs/oxlint/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "@robonen/tsconfig/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "."
|
||||
}
|
||||
}
|
||||
9
configs/oxlint/tsdown.config.ts
Normal file
9
configs/oxlint/tsdown.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'tsdown';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['esm', 'cjs'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
hash: false,
|
||||
});
|
||||
7
configs/oxlint/vitest.config.ts
Normal file
7
configs/oxlint/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
@@ -1,45 +1,27 @@
|
||||
# @robonen/tsconfig
|
||||
|
||||
Базовый конфигурационный файл для TypeScript
|
||||
Shared base TypeScript configuration.
|
||||
|
||||
## Установка
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pnpm install -D @robonen/tsconfig
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Extend from it in your `tsconfig.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "@robonen/tsconfig/tsconfig.json"
|
||||
}
|
||||
```
|
||||
|
||||
## Описание основных параметров
|
||||
## What's Included
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "Preserve", // использовать ту же версию модуля, что и сборщик
|
||||
"noEmit": true, // не генерировать файлы
|
||||
"moduleResolution": "Bundler", // разрешение модулей на основе сборщика
|
||||
"target": "ESNext", // целевая версия JavaScript
|
||||
|
||||
|
||||
"skipLibCheck": true, // не проверять типы, заданные во всех файлах описания типов (*.d.ts)
|
||||
"esModuleInterop": true, // создать хелперы __importStar и __importDefault для обеспечения совместимости с экосистемой Babel и включить allowSyntheticDefaultImports для совместимости с системой типов
|
||||
"allowSyntheticDefaultImports": true, // разрешить импортировать модули не имеющие внутри себя "import default"
|
||||
"allowJs": true, // разрешить импортировать файлы JavaScript
|
||||
"resolveJsonModule": true, // разрешить импортировать файлы JSON
|
||||
"moduleDetection": "force", // заставляет TypeScript рассматривать все файлы как модули. Это помогает избежать ошибок cannot redeclare block-scoped variable»
|
||||
"isolatedModules": true, // орабатывать каждый файл, как отдельный изолированный модуль
|
||||
"removeComments": false, // удалять комментарии из исходного кода
|
||||
"verbatimModuleSyntax": true, // сохранять синтаксис модулей в исходном коде (важно при импорте типов)
|
||||
"useDefineForClassFields": true, // использование классов стандарта TC39, а не TypeScript
|
||||
"strict": true, // включить все строгие проверки (noImplicitAny, noImplicitThis, alwaysStrict, strictNullChecks, strictFunctionTypes, strictPropertyInitialization)
|
||||
"noUncheckedIndexedAccess": true, // запрещает доступ к массиву или объекту без предварительной проверки того, определен ли он
|
||||
"declaration": true, // генерировать файлы описания типов (*.d.ts)
|
||||
|
||||
"composite": true, // указывает TypeScript создавать файлы .tsbuildinfo. Это сообщает TypeScript, что ваш проект является частью монорепозитория, а также помогает кэшировать сборки для более быстрой работы
|
||||
"sourceMap": true, // генерировать карту исходного кода
|
||||
"declarationMap": true // генерировать карту исходного кода для файлов описания типов (*.d.ts)
|
||||
}
|
||||
```
|
||||
- **Target / Module**: ESNext with Bundler resolution
|
||||
- **Strict mode**: `strict`, `noUncheckedIndexedAccess`
|
||||
- **Module safety**: `verbatimModuleSyntax`, `isolatedModules`
|
||||
- **Declarations**: `declaration` enabled
|
||||
- **Interop**: `esModuleInterop`, `allowJs`, `resolveJsonModule`
|
||||
|
||||
Reference in New Issue
Block a user