Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ab47642

Browse files
feat(cli): add --type-aware and --type-check flags for oxlint (#493)
* feat(cli): add --type-aware and --type-check flags for oxlint Adds proper CLI support for oxlint's type-aware linting features: - --type-aware: enables lint rules requiring type info - --type-check: enables TypeScript compiler diagnostics Both flags work with `check` and `fix` commands when using oxlint. * feat(init): add --type-aware flag for oxlint installs oxlint-tsgolint when --linter oxlint --type-aware * Create three-jars-report.md --------- Co-authored-by: Hayden Bleasel <[email protected]>
1 parent 8723576 commit ab47642

9 files changed

Lines changed: 486 additions & 9 deletions

File tree

.changeset/three-jars-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ultracite": patch
3+
---
4+
5+
add --type-aware and --type-check flags for oxlint

apps/docs/content/docs/usage.mdx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,44 @@ You can also apply unsafe fixes (fixes that may change code behavior):
5555
npx ultracite fix --unsafe
5656
```
5757

58+
### Type-Aware Linting (Oxlint)
59+
60+
When using Oxlint, you can enable type-aware linting rules that leverage TypeScript's type system.
61+
62+
To set up type-aware linting during initialization:
63+
64+
```bash title="Terminal"
65+
npx ultracite init --linter oxlint --type-aware
66+
```
67+
68+
This installs the required `oxlint-tsgolint` dependency automatically.
69+
70+
Then use the flags when checking or fixing:
71+
72+
```bash title="Terminal"
73+
npx ultracite check --type-aware
74+
npx ultracite fix --type-aware
75+
```
76+
77+
This enables rules like `no-floating-promises`, `no-misused-promises`, and `await-thenable` that catch bugs by analyzing types.
78+
79+
You can also enable TypeScript compiler diagnostics (experimental):
80+
81+
```bash title="Terminal"
82+
npx ultracite check --type-check
83+
npx ultracite fix --type-check
84+
```
85+
86+
Both flags can be combined:
87+
88+
```bash title="Terminal"
89+
npx ultracite fix --type-aware --type-check
90+
```
91+
92+
<Callout>
93+
These flags only apply when using Oxlint. They have no effect with Biome or ESLint.
94+
</Callout>
95+
5896
### Validating Setup
5997

6098
The `doctor` command checks your setup for issues and provides recommendations. This is useful to run after installing Ultracite to ensure everything is configured correctly:

packages/cli/__tests__/check.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,112 @@ describe("check", () => {
508508
expect(mockSpawn.mock.calls[0][0]).toContain("oxfmt");
509509
expect(mockSpawn.mock.calls[1][0]).toContain("oxlint");
510510
});
511+
512+
test("runs oxlint check with --type-aware flag", async () => {
513+
const mockSpawn = mock(() => ({ status: 0 }));
514+
mock.module("node:child_process", () => ({
515+
spawnSync: mockSpawn,
516+
execSync: mock(() => ""),
517+
}));
518+
mock.module("nypm", () => ({
519+
detectPackageManager: mock(async () => ({ name: "npm" })),
520+
dlxCommand: mock(
521+
(_pm, pkg, opts) =>
522+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
523+
),
524+
}));
525+
mock.module("../src/utils", () => ({
526+
detectLinter: mock(async () => "oxlint"),
527+
parseFilePaths,
528+
}));
529+
530+
await check([[], { linter: "oxlint", "type-aware": true }]);
531+
532+
expect(mockSpawn).toHaveBeenCalledTimes(2);
533+
const oxlintCall = mockSpawn.mock.calls[1];
534+
expect(oxlintCall[0]).toContain("--type-aware");
535+
});
536+
537+
test("runs oxlint check with --type-check flag", async () => {
538+
const mockSpawn = mock(() => ({ status: 0 }));
539+
mock.module("node:child_process", () => ({
540+
spawnSync: mockSpawn,
541+
execSync: mock(() => ""),
542+
}));
543+
mock.module("nypm", () => ({
544+
detectPackageManager: mock(async () => ({ name: "npm" })),
545+
dlxCommand: mock(
546+
(_pm, pkg, opts) =>
547+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
548+
),
549+
}));
550+
mock.module("../src/utils", () => ({
551+
detectLinter: mock(async () => "oxlint"),
552+
parseFilePaths,
553+
}));
554+
555+
await check([[], { linter: "oxlint", "type-check": true }]);
556+
557+
expect(mockSpawn).toHaveBeenCalledTimes(2);
558+
const oxlintCall = mockSpawn.mock.calls[1];
559+
expect(oxlintCall[0]).toContain("--type-check");
560+
});
561+
562+
test("runs oxlint check with both --type-aware and --type-check flags", async () => {
563+
const mockSpawn = mock(() => ({ status: 0 }));
564+
mock.module("node:child_process", () => ({
565+
spawnSync: mockSpawn,
566+
execSync: mock(() => ""),
567+
}));
568+
mock.module("nypm", () => ({
569+
detectPackageManager: mock(async () => ({ name: "npm" })),
570+
dlxCommand: mock(
571+
(_pm, pkg, opts) =>
572+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
573+
),
574+
}));
575+
mock.module("../src/utils", () => ({
576+
detectLinter: mock(async () => "oxlint"),
577+
parseFilePaths,
578+
}));
579+
580+
await check([
581+
[],
582+
{ linter: "oxlint", "type-aware": true, "type-check": true },
583+
]);
584+
585+
expect(mockSpawn).toHaveBeenCalledTimes(2);
586+
const oxlintCall = mockSpawn.mock.calls[1];
587+
expect(oxlintCall[0]).toContain("--type-aware");
588+
expect(oxlintCall[0]).toContain("--type-check");
589+
});
590+
591+
test("does not include type flags when options are false", async () => {
592+
const mockSpawn = mock(() => ({ status: 0 }));
593+
mock.module("node:child_process", () => ({
594+
spawnSync: mockSpawn,
595+
execSync: mock(() => ""),
596+
}));
597+
mock.module("nypm", () => ({
598+
detectPackageManager: mock(async () => ({ name: "npm" })),
599+
dlxCommand: mock(
600+
(_pm, pkg, opts) =>
601+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
602+
),
603+
}));
604+
mock.module("../src/utils", () => ({
605+
detectLinter: mock(async () => "oxlint"),
606+
parseFilePaths,
607+
}));
608+
609+
await check([
610+
[],
611+
{ linter: "oxlint", "type-aware": false, "type-check": false },
612+
]);
613+
614+
expect(mockSpawn).toHaveBeenCalledTimes(2);
615+
const oxlintCall = mockSpawn.mock.calls[1];
616+
expect(oxlintCall[0]).not.toContain("--type-aware");
617+
expect(oxlintCall[0]).not.toContain("--type-check");
618+
});
511619
});

packages/cli/__tests__/fix.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,4 +535,110 @@ describe("fix", () => {
535535
expect(mockSpawn.mock.calls[0][0]).toContain("oxfmt");
536536
expect(mockSpawn.mock.calls[1][0]).toContain("oxlint");
537537
});
538+
539+
test("runs oxlint fix with --type-aware flag", async () => {
540+
const mockSpawn = mock(() => ({ status: 0 }));
541+
mock.module("node:child_process", () => ({
542+
spawnSync: mockSpawn,
543+
execSync: mock(() => ""),
544+
}));
545+
mock.module("nypm", () => ({
546+
detectPackageManager: mock(async () => ({ name: "npm" })),
547+
dlxCommand: mock(
548+
(_pm, pkg, opts) =>
549+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
550+
),
551+
}));
552+
mock.module("../src/utils", () => ({
553+
detectLinter: mock(async () => "oxlint"),
554+
parseFilePaths,
555+
}));
556+
557+
await fix([], { linter: "oxlint", "type-aware": true });
558+
559+
expect(mockSpawn).toHaveBeenCalledTimes(2);
560+
const oxlintCall = mockSpawn.mock.calls[1];
561+
expect(oxlintCall[0]).toContain("--type-aware");
562+
});
563+
564+
test("runs oxlint fix with --type-check flag", async () => {
565+
const mockSpawn = mock(() => ({ status: 0 }));
566+
mock.module("node:child_process", () => ({
567+
spawnSync: mockSpawn,
568+
execSync: mock(() => ""),
569+
}));
570+
mock.module("nypm", () => ({
571+
detectPackageManager: mock(async () => ({ name: "npm" })),
572+
dlxCommand: mock(
573+
(_pm, pkg, opts) =>
574+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
575+
),
576+
}));
577+
mock.module("../src/utils", () => ({
578+
detectLinter: mock(async () => "oxlint"),
579+
parseFilePaths,
580+
}));
581+
582+
await fix([], { linter: "oxlint", "type-check": true });
583+
584+
expect(mockSpawn).toHaveBeenCalledTimes(2);
585+
const oxlintCall = mockSpawn.mock.calls[1];
586+
expect(oxlintCall[0]).toContain("--type-check");
587+
});
588+
589+
test("runs oxlint fix with both --type-aware and --type-check flags", async () => {
590+
const mockSpawn = mock(() => ({ status: 0 }));
591+
mock.module("node:child_process", () => ({
592+
spawnSync: mockSpawn,
593+
execSync: mock(() => ""),
594+
}));
595+
mock.module("nypm", () => ({
596+
detectPackageManager: mock(async () => ({ name: "npm" })),
597+
dlxCommand: mock(
598+
(_pm, pkg, opts) =>
599+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
600+
),
601+
}));
602+
mock.module("../src/utils", () => ({
603+
detectLinter: mock(async () => "oxlint"),
604+
parseFilePaths,
605+
}));
606+
607+
await fix([], { linter: "oxlint", "type-aware": true, "type-check": true });
608+
609+
expect(mockSpawn).toHaveBeenCalledTimes(2);
610+
const oxlintCall = mockSpawn.mock.calls[1];
611+
expect(oxlintCall[0]).toContain("--type-aware");
612+
expect(oxlintCall[0]).toContain("--type-check");
613+
});
614+
615+
test("does not include type flags when options are false", async () => {
616+
const mockSpawn = mock(() => ({ status: 0 }));
617+
mock.module("node:child_process", () => ({
618+
spawnSync: mockSpawn,
619+
execSync: mock(() => ""),
620+
}));
621+
mock.module("nypm", () => ({
622+
detectPackageManager: mock(async () => ({ name: "npm" })),
623+
dlxCommand: mock(
624+
(_pm, pkg, opts) =>
625+
`npx${pkg ? ` ${pkg}` : ""}${opts?.args ? ` ${opts.args.join(" ")}` : ""}`
626+
),
627+
}));
628+
mock.module("../src/utils", () => ({
629+
detectLinter: mock(async () => "oxlint"),
630+
parseFilePaths,
631+
}));
632+
633+
await fix([], {
634+
linter: "oxlint",
635+
"type-aware": false,
636+
"type-check": false,
637+
});
638+
639+
expect(mockSpawn).toHaveBeenCalledTimes(2);
640+
const oxlintCall = mockSpawn.mock.calls[1];
641+
expect(oxlintCall[0]).not.toContain("--type-aware");
642+
expect(oxlintCall[0]).not.toContain("--type-check");
643+
});
538644
});

0 commit comments

Comments
 (0)