Skip to content

Commit 235acdf

Browse files
authored
feat: use oxlint (#5)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Chores** - Replaced ESLint with Oxlint for linting and updated related configuration files. - Added Prettier for code formatting, including configuration and ignore files. - Introduced Husky pre-commit hook to enforce linting and formatting on staged files. - Updated continuous integration workflows to include Node.js 24 and standardized branch formatting. - **Refactor** - Minor code and test formatting improvements for readability. - Changed a type alias to an interface without altering functionality. - **Tests** - Reformatted test assertions for clarity; no changes to test logic. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ef3b9b7 commit 235acdf

10 files changed

Lines changed: 215 additions & 33 deletions

File tree

.eslintrc

Lines changed: 0 additions & 6 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ name: CI
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66
pull_request:
7-
branches: [ master ]
7+
branches: [master]
88

99
jobs:
1010
Job:
1111
name: Node.js
1212
uses: node-modules/github-actions/.github/workflows/node-test.yml@master
1313
with:
1414
os: 'ubuntu-latest, macos-latest, windows-latest'
15-
version: '20, 22'
15+
version: '20, 22, 24'
1616
secrets:
1717
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Release
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [master]
66

77
jobs:
88
release:

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npx lint-staged

.oxlintrc.json

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
{
2+
"$schema": "./node_modules/oxlint/configuration_schema.json",
3+
"env": {
4+
"node": true,
5+
"mocha": true
6+
},
7+
"categories": {
8+
"correctness": "error",
9+
"perf": "error",
10+
"nursery": "error",
11+
"restriction": "error",
12+
"style": "error",
13+
"pedantic": "error",
14+
"suspicious": "error"
15+
},
16+
"plugins": [
17+
"import",
18+
"typescript",
19+
"unicorn",
20+
"jsdoc",
21+
"node",
22+
"promise",
23+
"oxc"
24+
],
25+
"rules": {
26+
// eslint
27+
"constructor-super": "error",
28+
"getter-return": "error",
29+
"no-undef": "error",
30+
"no-unreachable": "error",
31+
"no-var": "error",
32+
"no-eq-null": "error",
33+
"no-await-in-loop": "allow",
34+
"eqeqeq": ["error", "smart"],
35+
"init-declarations": "allow",
36+
"curly": "allow",
37+
"no-ternary": "allow",
38+
"max-params": ["error", 5],
39+
"no-await-expression-member": "error",
40+
"no-continue": "allow",
41+
"guard-for-in": "allow",
42+
"func-style": "allow",
43+
"sort-imports": "allow",
44+
"yoda": "allow",
45+
"sort-keys": "allow",
46+
"no-magic-numbers": "allow",
47+
"no-duplicate-imports": "error",
48+
"no-multi-assign": "error",
49+
"func-names": "error",
50+
"default-param-last": "error",
51+
"prefer-object-spread": "error",
52+
"no-undefined": "allow",
53+
"no-plusplus": "allow",
54+
// maybe warn
55+
"no-console": "warn",
56+
"no-extraneous-class": "allow",
57+
"no-empty-function": "allow",
58+
"max-depth": ["error", 6],
59+
"max-lines-per-function": "allow",
60+
"no-lonely-if": "error",
61+
"max-lines": "allow",
62+
"require-await": "allow",
63+
"max-nested-callbacks": ["error", 5],
64+
"max-classes-per-file": "allow",
65+
"radix": "allow",
66+
"no-negated-condition": "error",
67+
"no-else-return": "error",
68+
"no-throw-literal": "error",
69+
70+
// import
71+
"import/exports-last": "allow",
72+
"import/max-dependencies": "allow",
73+
"import/no-cycle": "error",
74+
"import/no-anonymous-default-export": "allow",
75+
"import/no-namespace": "error",
76+
"import/named": "error",
77+
"import/export": "error",
78+
"import/no-default-export": "allow",
79+
"import/unambiguous": "error",
80+
"import/group-exports": "allow",
81+
82+
// promise
83+
"promise/no-return-wrap": "error",
84+
"promise/param-names": "error",
85+
"promise/prefer-await-to-callbacks": "error",
86+
"promise/prefer-await-to-then": "error",
87+
"promise/prefer-catch": "error",
88+
"promise/no-return-in-finally": "error",
89+
"promise/avoid-new": "error",
90+
91+
// unicorn
92+
"unicorn/error-message": "error",
93+
"unicorn/no-null": "allow",
94+
"unicorn/filename-case": "allow",
95+
"unicorn/prefer-structured-clone": "error",
96+
"unicorn/prefer-logical-operator-over-ternary": "error",
97+
"unicorn/prefer-number-properties": "error",
98+
"unicorn/prefer-array-some": "error",
99+
"unicorn/prefer-string-slice": "error",
100+
// "unicorn/no-null": "error",
101+
"unicorn/throw-new-error": "error",
102+
"unicorn/catch-error-name": "allow",
103+
"unicorn/prefer-spread": "allow",
104+
"unicorn/numeric-separators-style": "error",
105+
"unicorn/prefer-string-raw": "error",
106+
"unicorn/text-encoding-identifier-case": "error",
107+
"unicorn/no-array-for-each": "error",
108+
"unicorn/explicit-length-check": "error",
109+
"unicorn/no-lonely-if": "error",
110+
"unicorn/no-useless-undefined": "allow",
111+
"unicorn/prefer-date-now": "error",
112+
"unicorn/no-static-only-class": "allow",
113+
"unicorn/no-typeof-undefined": "error",
114+
"unicorn/prefer-negative-index": "error",
115+
"unicorn/no-anonymous-default-export": "allow",
116+
117+
// oxc
118+
"oxc/no-map-spread": "error",
119+
"oxc/no-rest-spread-properties": "allow",
120+
"oxc/no-optional-chaining": "allow",
121+
"oxc/no-async-await": "allow",
122+
123+
// typescript
124+
"typescript/explicit-function-return-type": "allow",
125+
"typescript/consistent-type-imports": "error",
126+
"typescript/consistent-type-definitions": "error",
127+
"typescript/consistent-indexed-object-style": "allow",
128+
"typescript/no-inferrable-types": "error",
129+
"typescript/array-type": "error",
130+
"typescript/no-non-null-assertion": "error",
131+
"typescript/no-explicit-any": "error",
132+
"typescript/no-import-type-side-effects": "error",
133+
"typescript/no-dynamic-delete": "error",
134+
"typescript/prefer-ts-expect-error": "error",
135+
"typescript/ban-ts-comment": "error",
136+
"typescript/prefer-enum-initializers": "error",
137+
138+
// jsdoc
139+
"jsdoc/require-returns": "allow",
140+
"jsdoc/require-param": "allow"
141+
},
142+
"ignorePatterns": ["index.d.ts", "test/fixtures/**", "__snapshots__"]
143+
}

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CHANGELOG.md
2+
__snapshots__

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "es5",
4+
"tabWidth": 2,
5+
"arrowParens": "avoid"
6+
}

package.json

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,31 @@
2424
"@eggjs/tsconfig": "1",
2525
"@types/node": "22",
2626
"@vitest/coverage-v8": "^3.0.5",
27-
"eslint": "8",
28-
"eslint-config-egg": "14",
27+
"husky": "^9.1.7",
28+
"oxlint": "^0.16.10",
29+
"prettier": "^3.5.3",
2930
"mm": "^4.0.2",
3031
"tshy": "3",
3132
"tshy-after": "1",
3233
"typescript": "5",
3334
"vitest": "^3.0.5"
3435
},
3536
"scripts": {
36-
"lint": "eslint --cache src test --ext .ts",
37+
"lint": "oxlint",
3738
"pretest": "npm run lint -- --fix",
38-
"test": "vitest",
39+
"test": "vitest run",
3940
"preci": "npm run lint",
4041
"ci": "vitest run --coverage",
4142
"postci": "npm run prepublishOnly",
42-
"prepublishOnly": "tshy && tshy-after && attw --pack"
43+
"prepublishOnly": "tshy && tshy-after && attw --pack",
44+
"prepare": "husky"
45+
},
46+
"lint-staged": {
47+
"*": "prettier --write --ignore-unknown --cache",
48+
"*.{ts,js,json,md,yml}": [
49+
"prettier --ignore-unknown --write",
50+
"oxlint --fix"
51+
]
4352
},
4453
"type": "module",
4554
"tshy": {

src/index.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export type ValueType = 'string' | 'boolean' | 'number';
22
export type DefaultValue = string | boolean | number | undefined;
33

4-
type TypeMap<D> = {
5-
'string': D extends undefined ? string | undefined : string;
6-
'boolean': D extends undefined ? boolean | undefined : boolean;
7-
'number': D extends undefined ? number | undefined : number;
8-
};
4+
interface TypeMap<D> {
5+
string: D extends undefined ? string | undefined : string;
6+
boolean: D extends undefined ? boolean | undefined : boolean;
7+
number: D extends undefined ? number | undefined : number;
8+
}
99

1010
type EnvReturn<V extends ValueType, D extends DefaultValue> = TypeMap<D>[V];
1111

@@ -19,7 +19,11 @@ export class EnvError extends Error {
1919
}
2020
}
2121

22-
export function env<V extends ValueType, D extends DefaultValue>(key: string, valueType: V, defaultValue?: D): EnvReturn<V, D> {
22+
export function env<V extends ValueType, D extends DefaultValue>(
23+
key: string,
24+
valueType: V,
25+
defaultValue?: D
26+
): EnvReturn<V, D> {
2327
let value = process.env[key];
2428
if (typeof value === 'string') {
2529
value = value.trim();
@@ -42,25 +46,25 @@ export function env<V extends ValueType, D extends DefaultValue>(key: string, va
4246
} else {
4347
throw new EnvError(
4448
`Invalid boolean value: ${value} on process.env.${key}`,
45-
'ERR_ENV_INVALID_BOOLEAN_VALUE',
49+
'ERR_ENV_INVALID_BOOLEAN_VALUE'
4650
);
4751
}
4852
return booleanValue as EnvReturn<V, D>;
4953
}
5054

5155
if (valueType === 'number') {
5256
const numberValue = Number(value);
53-
if (isNaN(numberValue)) {
57+
if (Number.isNaN(numberValue)) {
5458
throw new EnvError(
5559
`Invalid number value: ${value} on process.env.${key}`,
56-
'ERR_ENV_INVALID_NUMBER_VALUE',
60+
'ERR_ENV_INVALID_NUMBER_VALUE'
5761
);
5862
}
5963
return numberValue as EnvReturn<V, D>;
6064
}
6165

6266
throw new EnvError(
6367
`Invalid value type: ${valueType}`,
64-
'ERR_ENV_INVALID_VALUE_TYPE',
68+
'ERR_ENV_INVALID_VALUE_TYPE'
6569
);
6670
}

test/index.test.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import assert from 'node:assert/strict';
2+
23
import { test, beforeEach } from 'vitest';
34
import { mock, restore } from 'mm';
4-
import { env } from '../src/index.js';
5+
6+
import { env, type ValueType } from '../src/index.js';
57

68
beforeEach(restore);
79

@@ -29,7 +31,10 @@ test('should return env value if env is set to empty string', () => {
2931

3032
assert.equal(env('TEST_ENV_STRING', 'string', ''), '');
3133
assert.equal(env('TEST_ENV_STRING', 'string'), undefined);
32-
assert.equal(env('TEST_ENV_STRING', 'string', 'default string'), 'default string');
34+
assert.equal(
35+
env('TEST_ENV_STRING', 'string', 'default string'),
36+
'default string'
37+
);
3338
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean', true), true);
3439
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean', false), false);
3540
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean'), undefined);
@@ -44,7 +49,10 @@ test('should return env value if env is set to empty string', () => {
4449

4550
assert.equal(env('TEST_ENV_STRING', 'string', ''), '');
4651
assert.equal(env('TEST_ENV_STRING', 'string'), undefined);
47-
assert.equal(env('TEST_ENV_STRING', 'string', 'default string'), 'default string');
52+
assert.equal(
53+
env('TEST_ENV_STRING', 'string', 'default string'),
54+
'default string'
55+
);
4856
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean', true), true);
4957
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean', false), false);
5058
assert.equal(env('TEST_ENV_BOOLEAN', 'boolean'), undefined);
@@ -55,23 +63,38 @@ test('should return env value if env is set to empty string', () => {
5563

5664
test('should throw error if env is set to invalid value', () => {
5765
mock(process.env, 'TEST_ENV_BOOLEAN', 'invalid');
58-
assert.throws(() => env('TEST_ENV_BOOLEAN', 'boolean', false), /Invalid boolean value: invalid on process.env.TEST_ENV_BOOLEAN/);
66+
assert.throws(
67+
() => env('TEST_ENV_BOOLEAN', 'boolean', false),
68+
/Invalid boolean value: invalid on process.env.TEST_ENV_BOOLEAN/
69+
);
5970

6071
mock(process.env, 'TEST_ENV_NUMBER', 'invalid');
61-
assert.throws(() => env('TEST_ENV_NUMBER', 'number', 0), /Invalid number value: invalid on process.env.TEST_ENV_NUMBER/);
72+
assert.throws(
73+
() => env('TEST_ENV_NUMBER', 'number', 0),
74+
/Invalid number value: invalid on process.env.TEST_ENV_NUMBER/
75+
);
6276

6377
mock(process.env, 'TEST_ENV_NUMBER', 'abc');
64-
assert.throws(() => env('TEST_ENV_NUMBER', 'number', 0), /Invalid number value: abc on process.env.TEST_ENV_NUMBER/);
78+
assert.throws(
79+
() => env('TEST_ENV_NUMBER', 'number', 0),
80+
/Invalid number value: abc on process.env.TEST_ENV_NUMBER/
81+
);
6582
});
6683

6784
test('should throw error if value type is invalid', () => {
6885
mock(process.env, 'TEST_ENV_STRING', '123');
69-
assert.throws(() => (env as any)('TEST_ENV_STRING', 'float', 'default'), /Invalid value type: float/);
86+
assert.throws(
87+
() => env('TEST_ENV_STRING', 'float' as unknown as ValueType, 'default'),
88+
/Invalid value type: float/
89+
);
7090
});
7191

7292
test('should work with string value', () => {
7393
mock(process.env, 'TEST_ENV_STRING', 'http://localhost:3000');
74-
assert.equal(env('TEST_ENV_STRING', 'string', 'default'), 'http://localhost:3000');
94+
assert.equal(
95+
env('TEST_ENV_STRING', 'string', 'default'),
96+
'http://localhost:3000'
97+
);
7598

7699
mock(process.env, 'TEST_ENV_STRING', ' ');
77100
assert.equal(env('TEST_ENV_STRING', 'string', 'default'), 'default');

0 commit comments

Comments
 (0)