[PR #38] [MERGED] feat: type aware linting with tsgolint #259

Closed
opened 2026-05-06 12:38:49 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/38
Author: @ubugeeei
Created: 2/25/2026
Status: Merged
Merged: 2/27/2026
Merged by: @FredKSchott

Base: mainHead: type-aware-lint


📝 Commits (1)

  • 5c4cf54 feat: type aware linting with tsgolint

📊 Changes

12 files changed (+87 additions, -22 deletions)

View changed files

📝 package.json (+2 -1)
📝 packages/vinext/src/client/entry.ts (+1 -1)
📝 packages/vinext/src/config/next-config.ts (+1 -1)
📝 packages/vinext/src/shims/cache-runtime.ts (+2 -1)
📝 packages/vinext/src/shims/form.tsx (+1 -1)
📝 packages/vinext/src/shims/navigation.ts (+4 -4)
📝 packages/vinext/src/shims/router.ts (+2 -2)
📝 pnpm-lock.yaml (+67 -2)
📝 tests/e2e/app-router/nextjs-compat/external-redirect.spec.ts (+1 -1)
📝 tests/fixtures/app-basic/tsconfig.json (+0 -1)
📝 tests/fixtures/pages-basic/tsconfig.json (+0 -1)
📝 tests/safe-json.test.ts (+6 -6)

📄 Description

https://oxc.rs/docs/guide/usage/linter/type-aware.html

I've fixed some lint warnings

$ pnpm run lint

> vinext-monorepo@ lint /Users/nishimura/projects/oss/ubugeeei/vinex
> oxlint --deny-warnings --type-aware


  × typescript(tsconfig-error): Invalid tsconfig
    ╭─[tests/fixtures/app-basic/tsconfig.json:10:5]
  9 │     "types": ["vite/client", "@vitejs/plugin-rsc/types"],
 10 │     "baseUrl": ".",
    ·     ─────────
 11 │     "paths": {
    ╰────
  help: Option 'baseUrl' has been removed. Please remove it from your configuration.
        See https://github.com/oxc-project/tsgolint/issues/351 for more information.

  ⚠ typescript-eslint(restrict-template-expressions): Invalid type used in template literal expression.
     ╭─[packages/vinext/src/config/next-config.ts:298:51]
 297 │   if (output && output !== "export" && output !== "standalone") {
 298 │     console.warn(`[vinext] Unknown output mode "${output}", ignoring`);
     ·                                                   ───┬───
     ·                                                      ╰── Type: never
 299 │   }
     ╰────

  ⚠ typescript-eslint(no-base-to-string): 'a[1]' may use Object's default stringification format ('[object Object]') when stringified.
     ╭─[packages/vinext/src/shims/cache-runtime.ts:151:61]
 150 │   const entries: [string, FormDataEntryValue][] = [...reply.entries()];
 151 │   entries.sort((a, b) => a[0].localeCompare(b[0]) || String(a[1]).localeCompare(String(b[1])));
     ·                                                             ────
 152 │
     ╰────
  help: Consider picking a property (e.g. `user.name`), using a formatter (or `JSON.stringify`), or implementing a custom `toString()`/`toLocaleString()` on the type.

  ⚠ typescript-eslint(no-base-to-string): 'b[1]' may use Object's default stringification format ('[object Object]') when stringified.
     ╭─[packages/vinext/src/shims/cache-runtime.ts:151:88]
 150 │   const entries: [string, FormDataEntryValue][] = [...reply.entries()];
 151 │   entries.sort((a, b) => a[0].localeCompare(b[0]) || String(a[1]).localeCompare(String(b[1])));
     ·                                                                                        ────
 152 │
     ╰────
  help: Consider picking a property (e.g. `user.name`), using a formatter (or `JSON.stringify`), or implementing a custom `toString()`/`toLocaleString()` on the type.

  ⚠ typescript-eslint(restrict-template-expressions): Invalid type used in template literal expression.
    ╭─[packages/vinext/src/shims/form.tsx:72:20]
 71 │
 72 │     const url = `${action}?${params.toString()}`;
    ·                    ───┬───
    ·                       ╰── Type: string | ((formData: FormData) => void | Promise<void>)
 73 │
    ╰────

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/navigation.ts:396:5]
 395 │         // that runs after all synchronous event listeners have completed.
 396 │ ╭─▶     Promise.resolve().then(() => {
 397 │ │         const pending: Promise<void> | null = (window as any).__VINEXT_RSC_PENDING__ ?? null;
 398 │ │
 399 │ │         if (pending) {
 400 │ │           // Wait for the RSC navigation to finish rendering, then scroll.
 401 │ │           pending.then(() => {
 402 │ │             requestAnimationFrame(() => {
 403 │ │               window.scrollTo(x, y);
 404 │ │             });
 405 │ │           });
 406 │ │         } else {
 407 │ │           // No RSC navigation in flight (Pages Router or already settled).
 408 │ │           requestAnimationFrame(() => {
 409 │ │             window.scrollTo(x, y);
 410 │ │           });
 411 │ │         }
 412 │ ╰─▶     });
 413 │       }
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/navigation.ts:401:9]
 400 │             // Wait for the RSC navigation to finish rendering, then scroll.
 401 │ ╭─▶         pending.then(() => {
 402 │ │             requestAnimationFrame(() => {
 403 │ │               window.scrollTo(x, y);
 404 │ │             });
 405 │ ╰─▶         });
 406 │           } else {
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/navigation.ts:491:7]
 490 │       if (isServer) return;
 491 │       navigateImpl(href, "push", options?.scroll !== false);
     ·       ──────────────────────────────────────────────────────
 492 │     },
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/navigation.ts:495:7]
 494 │       if (isServer) return;
 495 │       navigateImpl(href, "replace", options?.scroll !== false);
     ·       ─────────────────────────────────────────────────────────
 496 │     },
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/router.ts:398:7]
 397 │           // Re-render with the new page on back/forward navigation
 398 │ ╭─▶       navigateClient(window.location.pathname + window.location.search).then(() => {
 399 │ │           restoreScrollPosition(e.state);
 400 │ ╰─▶       });
 401 │         };
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
     ╭─[packages/vinext/src/shims/router.ts:582:5]
 581 │         routerEvents.emit("routeChangeStart", appUrl);
 582 │ ╭─▶     navigateClient(browserUrl).then(() => {
 583 │ │         routerEvents.emit("routeChangeComplete", appUrl);
 584 │ │         restoreScrollPosition(e.state);
 585 │ │         window.dispatchEvent(new CustomEvent("vinext:navigate"));
 586 │ ╰─▶     });
 587 │       });
     ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
    ╭─[packages/vinext/src/client/entry.ts:90:1]
 89 │
 90 │ hydrate();
    · ──────────
    ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:224:22]
 223 │       // eslint-disable-next-line no-new-func
 224 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 225 │       expect(parsed).toEqual(data);
     ╰────

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:246:22]
 245 │       // eslint-disable-next-line no-new-func
 246 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 247 │       expect(parsed).toEqual(data);
     ╰────

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:268:22]
 267 │       // eslint-disable-next-line no-new-func
 268 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 269 │       expect(parsed).toEqual(data);
     ╰────

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:294:22]
 293 │       // eslint-disable-next-line no-new-func
 294 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 295 │       expect(parsed).toBe(long);
     ╰────

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:305:22]
 304 │       // eslint-disable-next-line no-new-func
 305 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 306 │       expect(parsed).toBe(input);
     ╰────

  ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions.
     ╭─[tests/safe-json.test.ts:317:22]
 316 │       // eslint-disable-next-line no-new-func
 317 │       const parsed = new Function(`return (${result})`)();
     ·                      ──────────────────────────────────
 318 │       expect(parsed).toEqual(data);
     ╰────

  ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore.
    ╭─[tests/e2e/app-router/nextjs-compat/external-redirect.spec.ts:28:7]
 27 │           externalRedirectUrl = route.request().url();
 28 │ ╭─▶       route.fulfill({
 29 │ │           status: 200,
 30 │ │           contentType: "text/html",
 31 │ │           body: "<html><body>External page</body></html>",
 32 │ ╰─▶       });
 33 │         });
    ╰────
  help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator.

  × typescript(tsconfig-error): Invalid tsconfig
    ╭─[tests/fixtures/pages-basic/tsconfig.json:9:5]
  8 │     "skipLibCheck": true,
  9 │     "baseUrl": ".",
    ·     ─────────
 10 │     "paths": {
    ╰────
  help: Option 'baseUrl' has been removed. Please remove it from your configuration.
        See https://github.com/oxc-project/tsgolint/issues/351 for more information.

Found 18 warnings and 2 errors.
Finished in 1.4s on 465 files with 107 rules using 10 threads.
 ELIFECYCLE  Command failed with exit code 1.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/38 **Author:** [@ubugeeei](https://github.com/ubugeeei) **Created:** 2/25/2026 **Status:** ✅ Merged **Merged:** 2/27/2026 **Merged by:** [@FredKSchott](https://github.com/FredKSchott) **Base:** `main` ← **Head:** `type-aware-lint` --- ### 📝 Commits (1) - [`5c4cf54`](https://github.com/cloudflare/vinext/commit/5c4cf54ad42228154dff1befe234b357e994148a) feat: type aware linting with tsgolint ### 📊 Changes **12 files changed** (+87 additions, -22 deletions) <details> <summary>View changed files</summary> 📝 `package.json` (+2 -1) 📝 `packages/vinext/src/client/entry.ts` (+1 -1) 📝 `packages/vinext/src/config/next-config.ts` (+1 -1) 📝 `packages/vinext/src/shims/cache-runtime.ts` (+2 -1) 📝 `packages/vinext/src/shims/form.tsx` (+1 -1) 📝 `packages/vinext/src/shims/navigation.ts` (+4 -4) 📝 `packages/vinext/src/shims/router.ts` (+2 -2) 📝 `pnpm-lock.yaml` (+67 -2) 📝 `tests/e2e/app-router/nextjs-compat/external-redirect.spec.ts` (+1 -1) 📝 `tests/fixtures/app-basic/tsconfig.json` (+0 -1) 📝 `tests/fixtures/pages-basic/tsconfig.json` (+0 -1) 📝 `tests/safe-json.test.ts` (+6 -6) </details> ### 📄 Description https://oxc.rs/docs/guide/usage/linter/type-aware.html I've fixed some lint warnings ``` $ pnpm run lint > vinext-monorepo@ lint /Users/nishimura/projects/oss/ubugeeei/vinex > oxlint --deny-warnings --type-aware × typescript(tsconfig-error): Invalid tsconfig ╭─[tests/fixtures/app-basic/tsconfig.json:10:5] 9 │ "types": ["vite/client", "@vitejs/plugin-rsc/types"], 10 │ "baseUrl": ".", · ───────── 11 │ "paths": { ╰──── help: Option 'baseUrl' has been removed. Please remove it from your configuration. See https://github.com/oxc-project/tsgolint/issues/351 for more information. ⚠ typescript-eslint(restrict-template-expressions): Invalid type used in template literal expression. ╭─[packages/vinext/src/config/next-config.ts:298:51] 297 │ if (output && output !== "export" && output !== "standalone") { 298 │ console.warn(`[vinext] Unknown output mode "${output}", ignoring`); · ───┬─── · ╰── Type: never 299 │ } ╰──── ⚠ typescript-eslint(no-base-to-string): 'a[1]' may use Object's default stringification format ('[object Object]') when stringified. ╭─[packages/vinext/src/shims/cache-runtime.ts:151:61] 150 │ const entries: [string, FormDataEntryValue][] = [...reply.entries()]; 151 │ entries.sort((a, b) => a[0].localeCompare(b[0]) || String(a[1]).localeCompare(String(b[1]))); · ──── 152 │ ╰──── help: Consider picking a property (e.g. `user.name`), using a formatter (or `JSON.stringify`), or implementing a custom `toString()`/`toLocaleString()` on the type. ⚠ typescript-eslint(no-base-to-string): 'b[1]' may use Object's default stringification format ('[object Object]') when stringified. ╭─[packages/vinext/src/shims/cache-runtime.ts:151:88] 150 │ const entries: [string, FormDataEntryValue][] = [...reply.entries()]; 151 │ entries.sort((a, b) => a[0].localeCompare(b[0]) || String(a[1]).localeCompare(String(b[1]))); · ──── 152 │ ╰──── help: Consider picking a property (e.g. `user.name`), using a formatter (or `JSON.stringify`), or implementing a custom `toString()`/`toLocaleString()` on the type. ⚠ typescript-eslint(restrict-template-expressions): Invalid type used in template literal expression. ╭─[packages/vinext/src/shims/form.tsx:72:20] 71 │ 72 │ const url = `${action}?${params.toString()}`; · ───┬─── · ╰── Type: string | ((formData: FormData) => void | Promise<void>) 73 │ ╰──── ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/navigation.ts:396:5] 395 │ // that runs after all synchronous event listeners have completed. 396 │ ╭─▶ Promise.resolve().then(() => { 397 │ │ const pending: Promise<void> | null = (window as any).__VINEXT_RSC_PENDING__ ?? null; 398 │ │ 399 │ │ if (pending) { 400 │ │ // Wait for the RSC navigation to finish rendering, then scroll. 401 │ │ pending.then(() => { 402 │ │ requestAnimationFrame(() => { 403 │ │ window.scrollTo(x, y); 404 │ │ }); 405 │ │ }); 406 │ │ } else { 407 │ │ // No RSC navigation in flight (Pages Router or already settled). 408 │ │ requestAnimationFrame(() => { 409 │ │ window.scrollTo(x, y); 410 │ │ }); 411 │ │ } 412 │ ╰─▶ }); 413 │ } ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/navigation.ts:401:9] 400 │ // Wait for the RSC navigation to finish rendering, then scroll. 401 │ ╭─▶ pending.then(() => { 402 │ │ requestAnimationFrame(() => { 403 │ │ window.scrollTo(x, y); 404 │ │ }); 405 │ ╰─▶ }); 406 │ } else { ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/navigation.ts:491:7] 490 │ if (isServer) return; 491 │ navigateImpl(href, "push", options?.scroll !== false); · ────────────────────────────────────────────────────── 492 │ }, ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/navigation.ts:495:7] 494 │ if (isServer) return; 495 │ navigateImpl(href, "replace", options?.scroll !== false); · ───────────────────────────────────────────────────────── 496 │ }, ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/router.ts:398:7] 397 │ // Re-render with the new page on back/forward navigation 398 │ ╭─▶ navigateClient(window.location.pathname + window.location.search).then(() => { 399 │ │ restoreScrollPosition(e.state); 400 │ ╰─▶ }); 401 │ }; ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/shims/router.ts:582:5] 581 │ routerEvents.emit("routeChangeStart", appUrl); 582 │ ╭─▶ navigateClient(browserUrl).then(() => { 583 │ │ routerEvents.emit("routeChangeComplete", appUrl); 584 │ │ restoreScrollPosition(e.state); 585 │ │ window.dispatchEvent(new CustomEvent("vinext:navigate")); 586 │ ╰─▶ }); 587 │ }); ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[packages/vinext/src/client/entry.ts:90:1] 89 │ 90 │ hydrate(); · ────────── ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:224:22] 223 │ // eslint-disable-next-line no-new-func 224 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 225 │ expect(parsed).toEqual(data); ╰──── ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:246:22] 245 │ // eslint-disable-next-line no-new-func 246 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 247 │ expect(parsed).toEqual(data); ╰──── ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:268:22] 267 │ // eslint-disable-next-line no-new-func 268 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 269 │ expect(parsed).toEqual(data); ╰──── ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:294:22] 293 │ // eslint-disable-next-line no-new-func 294 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 295 │ expect(parsed).toBe(long); ╰──── ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:305:22] 304 │ // eslint-disable-next-line no-new-func 305 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 306 │ expect(parsed).toBe(input); ╰──── ⚠ typescript-eslint(no-implied-eval): Implied eval. Do not use the Function constructor to create functions. ╭─[tests/safe-json.test.ts:317:22] 316 │ // eslint-disable-next-line no-new-func 317 │ const parsed = new Function(`return (${result})`)(); · ────────────────────────────────── 318 │ expect(parsed).toEqual(data); ╰──── ⚠ typescript-eslint(no-floating-promises): Promises must be awaited, add void operator to ignore. ╭─[tests/e2e/app-router/nextjs-compat/external-redirect.spec.ts:28:7] 27 │ externalRedirectUrl = route.request().url(); 28 │ ╭─▶ route.fulfill({ 29 │ │ status: 200, 30 │ │ contentType: "text/html", 31 │ │ body: "<html><body>External page</body></html>", 32 │ ╰─▶ }); 33 │ }); ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. × typescript(tsconfig-error): Invalid tsconfig ╭─[tests/fixtures/pages-basic/tsconfig.json:9:5] 8 │ "skipLibCheck": true, 9 │ "baseUrl": ".", · ───────── 10 │ "paths": { ╰──── help: Option 'baseUrl' has been removed. Please remove it from your configuration. See https://github.com/oxc-project/tsgolint/issues/351 for more information. Found 18 warnings and 2 errors. Finished in 1.4s on 465 files with 107 rules using 10 threads.  ELIFECYCLE  Command failed with exit code 1. ``` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 12:38:49 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#259
No description provided.