Skip to content

Commit db91b09

Browse files
committed
feat(linter/plugins): oxlint export types (#14163)
`oxlint` package export TS types. `defineRule` and `definePlugin` now give you type-safety and intellisense. Writing plugins in TS is only supported on versions of NodeJS which support type-stripping. Have not added a library (e.g. Jiti) to support older NodeJS versions. Probably some of the types are wrong, or incomplete, and we should maybe export more types. But I think this is a decent start.
1 parent e9a14d1 commit db91b09

File tree

15 files changed

+101
-67
lines changed

15 files changed

+101
-67
lines changed

apps/oxlint/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"type": "module",
55
"main": "dist/index.js",
66
"bin": "dist/cli.js",
7+
"types": "dist/index.d.ts",
78
"scripts": {
89
"build": "pnpm run build-napi-release && pnpm run build-js",
910
"build-dev": "pnpm run build-napi && pnpm run build-js",
@@ -34,9 +35,13 @@
3435
"files": [
3536
"dist"
3637
],
38+
"dependencies": {
39+
"@oxc-project/types": "workspace:^"
40+
},
3741
"devDependencies": {
3842
"eslint": "^9.36.0",
3943
"execa": "^9.6.0",
44+
"jiti": "^2.6.0",
4045
"tsdown": "0.15.1",
4146
"typescript": "catalog:",
4247
"vitest": "catalog:"

apps/oxlint/src-js/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import type { Context } from './plugins/context.ts';
22
import type { CreateOnceRule, Plugin, Rule } from './plugins/load.ts';
33
import type { BeforeHook, Visitor, VisitorWithHooks } from './plugins/types.ts';
44

5+
export type { Context, Diagnostic } from './plugins/context.ts';
6+
export type { Fix, Fixer, FixFn, NodeOrToken, Range } from './plugins/fix.ts';
7+
export type { CreateOnceRule, CreateRule, Plugin, Rule } from './plugins/load.ts';
8+
export type { AfterHook, BeforeHook, RuleMeta, Visitor, VisitorWithHooks } from './plugins/types.ts';
9+
510
const { defineProperty, getPrototypeOf, hasOwn, setPrototypeOf, create: ObjectCreate } = Object;
611

712
const dummyOptions: unknown[] = [],

apps/oxlint/src-js/plugins/fix.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ export type FixFn = (
1717
// Type of a fix, as returned by `fix` function.
1818
export type Fix = { range: Range; text: string };
1919

20-
type Range = [number, number];
20+
export type Range = [number, number];
2121

2222
// Currently we only support `Node`s, but will add support for `Token`s later
23-
interface NodeOrToken {
23+
export interface NodeOrToken {
2424
start: number;
2525
end: number;
2626
}
@@ -60,7 +60,7 @@ const FIXER = Object.freeze({
6060
},
6161
});
6262

63-
type Fixer = typeof FIXER;
63+
export type Fixer = typeof FIXER;
6464

6565
/**
6666
* Get fixes from a `Diagnostic`.

apps/oxlint/src-js/plugins/load.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface Plugin {
2020
// If `createOnce` method is present, `create` is ignored.
2121
export type Rule = CreateRule | CreateOnceRule;
2222

23-
interface CreateRule {
23+
export interface CreateRule {
2424
meta?: RuleMeta;
2525
create: (context: Context) => Visitor;
2626
}

apps/oxlint/test/fixtures/definePlugin/.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"jsPlugins": ["./plugin.js"],
2+
"jsPlugins": ["./plugin.ts"],
33
"categories": { "correctness": "off" },
44
"rules": {
55
"define-plugin-plugin/create": "error",

apps/oxlint/test/fixtures/definePlugin/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import plugin from './plugin.js';
1+
import plugin from './plugin.ts';
22

33
export default [
44
{

apps/oxlint/test/fixtures/definePlugin/plugin.js renamed to apps/oxlint/test/fixtures/definePlugin/plugin.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { sep } from 'node:path';
22
import { definePlugin } from '../../../dist/index.js';
3+
import type { Rule } from '../../../dist/index.js';
34

45
// `loc` is required for ESLint
56
const SPAN = {
@@ -14,10 +15,10 @@ const SPAN = {
1415
const DIR_PATH_LEN = import.meta.dirname.length + 1;
1516

1617
const relativePath = sep === '/'
17-
? path => path.slice(DIR_PATH_LEN)
18-
: path => path.slice(DIR_PATH_LEN).replace(/\\/g, '/');
18+
? (path: string) => path.slice(DIR_PATH_LEN)
19+
: (path: string) => path.slice(DIR_PATH_LEN).replace(/\\/g, '/');
1920

20-
const createRule = {
21+
const createRule: Rule = {
2122
create(context) {
2223
context.report({ message: `create body:\nthis === rule: ${this === createRule}`, node: SPAN });
2324

@@ -37,18 +38,19 @@ const createRule = {
3738
// adds to the rule.
3839
let createOnceCallCount = 0;
3940

40-
const createOnceRule = {
41+
const createOnceRule: Rule = {
4142
createOnce(context) {
4243
createOnceCallCount++;
4344

4445
// `fileNum` should be different for each file.
4546
// `identNum` should start at 1 for each file.
46-
let fileNum = 0, identNum;
47+
let fileNum = 0, identNum: number;
4748
// Note: Files are processed in unpredictable order, so `files/1.js` may be `fileNum` 1 or 2.
4849
// Therefore, collect all visits and check them in `after` hook of the 2nd file.
49-
const visits = [];
50+
const visits: { fileNum: number; identNum: number }[] = [];
5051

5152
// `this` should be the rule object
53+
// oxlint-disable-next-line typescript-eslint/no-this-alias
5254
const topLevelThis = this;
5355

5456
return {
@@ -106,7 +108,7 @@ const createOnceRule = {
106108
};
107109

108110
// Tests that `before` hook returning `false` disables visiting AST for the file.
109-
const createOnceBeforeFalseRule = {
111+
const createOnceBeforeFalseRule: Rule = {
110112
createOnce(context) {
111113
return {
112114
before() {
@@ -139,7 +141,7 @@ const createOnceBeforeFalseRule = {
139141

140142
// These 3 rules test that `createOnce` without `before` and `after` hooks works correctly.
141143

142-
const createOnceBeforeOnlyRule = {
144+
const createOnceBeforeOnlyRule: Rule = {
143145
createOnce(context) {
144146
return {
145147
before() {
@@ -160,7 +162,7 @@ const createOnceBeforeOnlyRule = {
160162
},
161163
};
162164

163-
const createOnceAfterOnlyRule = {
165+
const createOnceAfterOnlyRule: Rule = {
164166
createOnce(context) {
165167
return {
166168
Identifier(node) {
@@ -181,7 +183,7 @@ const createOnceAfterOnlyRule = {
181183
},
182184
};
183185

184-
const createOnceNoHooksRule = {
186+
const createOnceNoHooksRule: Rule = {
185187
createOnce(context) {
186188
return {
187189
Identifier(node) {

apps/oxlint/test/fixtures/definePlugin_and_defineRule/.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"jsPlugins": ["./plugin.js"],
2+
"jsPlugins": ["./plugin.ts"],
33
"categories": { "correctness": "off" },
44
"rules": {
55
"define-plugin-and-rule-plugin/create": "error",

apps/oxlint/test/fixtures/definePlugin_and_defineRule/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import plugin from './plugin.js';
1+
import plugin from './plugin.ts';
22

33
export default [
44
{

apps/oxlint/test/fixtures/definePlugin_and_defineRule/plugin.js renamed to apps/oxlint/test/fixtures/definePlugin_and_defineRule/plugin.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const SPAN = {
1414
const DIR_PATH_LEN = import.meta.dirname.length + 1;
1515

1616
const relativePath = sep === '/'
17-
? path => path.slice(DIR_PATH_LEN)
18-
: path => path.slice(DIR_PATH_LEN).replace(/\\/g, '/');
17+
? (path: string) => path.slice(DIR_PATH_LEN)
18+
: (path: string) => path.slice(DIR_PATH_LEN).replace(/\\/g, '/');
1919

2020
const createRule = defineRule({
2121
create(context) {
@@ -42,12 +42,13 @@ const createOnceRule = defineRule({
4242

4343
// `fileNum` should be different for each file.
4444
// `identNum` should start at 1 for each file.
45-
let fileNum = 0, identNum;
45+
let fileNum = 0, identNum: number;
4646
// Note: Files are processed in unpredictable order, so `files/1.js` may be `fileNum` 1 or 2.
4747
// Therefore, collect all visits and check them in `after` hook of the 2nd file.
48-
const visits = [];
48+
const visits: { fileNum: number; identNum: number }[] = [];
4949

5050
// `this` should be the rule object returned by `defineRule`
51+
// oxlint-disable-next-line typescript-eslint/no-this-alias
5152
const topLevelThis = this;
5253

5354
return {

0 commit comments

Comments
 (0)