Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/errors/OXDT0001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
outline: deep
---
# OXDT0001: Failed to Create Lint Result

## Message
> Failed to create a lint result: `{reason}`

## Cause
Oxlint could not start, its output could not be parsed, or the lint result could not be written to the project directory.

## Example
Clicking `Run Lint` when oxlint is not installed in the project.

## Fix
Ensure oxlint is installed, its configuration is valid, and the project directory is writable.

## Source
- [`packages/oxc/src/node/rpc/functions/oxlint-run.ts`](https://github.com/vitejs/devtools/blob/main/packages/oxc/src/node/rpc/functions/oxlint-run.ts) — Runs oxlint and saves the lint result.
19 changes: 19 additions & 0 deletions docs/errors/OXDT0002.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
outline: deep
---
# OXDT0002: Invalid Lint Result ID

## Message
> Invalid lint result ID "`{resultId}`".

## Cause
A non-numeric ID was provided when reading or deleting a lint result.

## Example
Passing `../other-directory` as the result ID in an RPC call.

## Fix
Use a numeric ID shown in the lint result list.

## Source
- [`packages/oxc/src/node/utils/lint-results-manager.ts`](https://github.com/vitejs/devtools/blob/main/packages/oxc/src/node/utils/lint-results-manager.ts) — Validates the result ID before file operations.
19 changes: 19 additions & 0 deletions docs/errors/OXDT0003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
outline: deep
---
# OXDT0003: Failed to Delete Lint Result

## Message
> Failed to delete lint result "`{resultId}`": `{reason}`

## Cause
The lint result does not exist, or the project directory does not allow the file to be deleted.

## Example
Deleting a lint result that another process has already removed.

## Fix
Refresh the lint result list and ensure the project directory is writable.

## Source
- [`packages/oxc/src/node/rpc/functions/oxlint-delete-result.ts`](https://github.com/vitejs/devtools/blob/main/packages/oxc/src/node/rpc/functions/oxlint-delete-result.ts) — Deletes the local lint result.
10 changes: 10 additions & 0 deletions docs/errors/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ Emitted by `@vitejs/devtools-vitest`.
|------|-------|-------|
| [VTDT0001](./VTDT0001) | error | Vitest UI Install Failed |
| [VTDT0002](./VTDT0002) | error | Vitest UI Server Unreachable |

## Oxc DevTools (OXDT)

Emitted by `@vitejs/devtools-oxc`.

| Code | Level | Title |
|------|-------|-------|
| [OXDT0001](./OXDT0001) | error | Failed to Create Lint Result |
| [OXDT0002](./OXDT0002) | error | Invalid Lint Result ID |
| [OXDT0003](./OXDT0003) | error | Failed to Delete Lint Result |
2 changes: 1 addition & 1 deletion docs/oxc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ export default defineConfig({
})
```

It also runs standalone from the CLI — generate lint logs with `npx @vitejs/devtools-oxc lint`, then launch the UI with `npx @vitejs/devtools-oxc`.
It also runs standalone from the CLI with `npx @vitejs/devtools-oxc`.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"watch": "pnpm -r run watch",
"dev:rolldown": "pnpm -C packages/rolldown run dev",
"dev:vite": "pnpm -C packages/vite run dev",
"dev:oxc": "pnpm -C packages/oxc run dev",
"docs": "pnpm -C docs run docs",
"docs:build": "pnpm build && pnpm -C docs run docs:build",
"docs:serve": "pnpm -C docs run docs:serve",
Expand Down
6 changes: 0 additions & 6 deletions packages/oxc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ yarn add -D @vitejs/devtools-oxc
bun add -D @vitejs/devtools-oxc
```

### Generate lint logs

```sh
npx @vitejs/devtools-oxc lint
```

### Launch UI

```sh
Expand Down
4 changes: 1 addition & 3 deletions packages/oxc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,9 @@
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"@clack/prompts": "catalog:inlined",
"@vitejs/devtools-kit": "workspace:*",
"ansis": "catalog:deps",
"cac": "catalog:deps",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am actually ok with keeping gunshi for a bit of diversity

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cac is more lightweight than gunshi. Given that the current CLI is relatively simple, I think it's a better fit for now. We can consider switching to gunshi if the CLI grows more complex in the future.

"devframe": "catalog:deps",
"gunshi": "catalog:deps",
"nostics": "catalog:deps",
"pathe": "catalog:deps",
"tinyexec": "catalog:deps"
Expand Down
4 changes: 1 addition & 3 deletions packages/oxc/src/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,5 @@ connect()
{{ connectionState.error }}
</div>
<VisualLoading v-else-if="!connectionState.connected" text="Connecting..." />
<main v-else container mx-auto p4>
<NuxtPage />
</main>
<NuxtPage v-else />
</template>
Original file line number Diff line number Diff line change
@@ -1,26 +1,46 @@
<script setup lang="ts">
import ContainerCard from '@vitejs/devtools-ui/components/Container/ContainerCard.vue'
import type { Meta } from '../../../src/types'
import type { LintResultMeta } from '../../../src/types'

const { meta } = defineProps<{
meta: Meta
const { result } = defineProps<{
result: LintResultMeta
}>()

const failed = computed(() => {
return meta.summary.files_with_issues > 0
})
const emit = defineEmits<{
delete: [resultId: string]
}>()

const failed = computed(() => result.summary.files_with_issues > 0)
</script>

<template>
<NuxtLink w-full :to="`/lint/report/${meta.timestamp}`">
<ContainerCard p4 cursor-pointer hover:bg-active>
<ContainerCard relative p4 hover:bg-active>
<NuxtLink
absolute
inset-0
:to="{ path: '/oxlint/lint', query: { result: String(result.timestamp) } }"
:aria-label="`Open lint result ${result.timestamp}`"
/>

<div relative pointer-events-none>
<div flex justify-between gap-2 font-mono op-fade>
<div flex items-center gap-1>
<div i-ph-hash-duotone />
<span text-sm>{{ meta.timestamp }}</span>
<span text-sm>{{ result.timestamp }}</span>
</div>

{{ useTimeAgo(meta.timestamp) }}
<div flex items-center gap-2>
{{ useTimeAgo(result.timestamp) }}
<button
type="button"
pointer-events-auto
hover:text-red
:aria-label="`Delete lint result ${result.timestamp}`"
@click="emit('delete', String(result.timestamp))"
>
<div i-ph-trash-duotone />
</button>
</div>
</div>

<div flex justify-between items-center mt4>
Expand All @@ -37,12 +57,12 @@ const failed = computed(() => {
font-mono
>
<div i-ph-file-duotone />
{{ meta.summary.number_of_files }}
{{ result.summary.number_of_files }}
</span>

<div v-if="failed" flex items-center gap-2>
<span
v-if="meta.summary.error_count > 0"
v-if="result.summary.error_count > 0"
badge-color-red
inline-flex
items-center
Expand All @@ -55,11 +75,11 @@ const failed = computed(() => {
font-mono
>
<div i-ph-x-circle-duotone />
{{ meta.summary.error_count }}
{{ result.summary.error_count }}
</span>

<span
v-if="meta.summary.warning_count > 0"
v-if="result.summary.warning_count > 0"
badge-color-amber
inline-flex
items-center
Expand All @@ -72,7 +92,7 @@ const failed = computed(() => {
font-mono
>
<div i-ph-warning-circle-duotone />
{{ meta.summary.warning_count }}
{{ result.summary.warning_count }}
</span>
</div>

Expand All @@ -93,6 +113,6 @@ const failed = computed(() => {
Passed
</span>
</div>
</ContainerCard>
</NuxtLink>
</div>
</ContainerCard>
</template>
105 changes: 105 additions & 0 deletions packages/oxc/src/app/components/LintResultDetailsLoader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import DisplayCloseButton from '@vitejs/devtools-ui/components/Display/DisplayCloseButton.vue'
import VisualEmptyState from '@vitejs/devtools-ui/components/Visual/VisualEmptyState.vue'
import { useAsyncState, useDebounceFn } from '@vueuse/core'
import { computed, ref, watch } from 'vue'
import { isMatch } from 'picomatch'
import { useRpc } from '#imports'

const props = defineProps<{
resultId: string
}>()

const emit = defineEmits<{
close: []
}>()

const rpc = useRpc()
const { state: lintResult, isLoading } = useAsyncState(
() => rpc.value.call('devtools-oxc:get-lint-result', { resultId: props.resultId }),
null,
)

const showEmpty = computed(() => lintResult.value?.logs.files.length === 0)

const totalIssues = computed(() => {
if (!lintResult.value?.logs) return 0

return lintResult.value.logs.files.reduce(
(sum, file) => sum + file.lines.reduce((lineSum, line) => lineSum + line.messages.length, 0),
0,
)
})

const showSummary = computed(() => !!lintResult.value?.meta.summary)
const search = ref('')
const debouncedSearch = ref('')
const debouncedUpdateSearch = useDebounceFn((value: string) => {
debouncedSearch.value = value
}, 300)

function resetSearch() {
search.value = ''
debouncedSearch.value = ''
}

watch(search, value => debouncedUpdateSearch(value), { immediate: true })

const filteredFiles = computed(() => {
if (!lintResult.value?.logs?.files) return []

const searchTerm = debouncedSearch.value.trim()
if (!searchTerm) return lintResult.value.logs.files

try {
return lintResult.value.logs.files.filter(file =>
isMatch(file.filename, searchTerm, { contains: true }),
)
} catch {
return lintResult.value.logs.files.filter(file => file.filename.includes(searchTerm))
}
})

const showFiles = computed(() => filteredFiles.value.length > 0)
</script>

<template>
<div relative h-full w-full>
<DisplayCloseButton absolute right-1 top-1 z-panel-content bg-glass @click="emit('close')" />

<VisualLoading v-if="isLoading" text="Loading lint result..." />
<div v-else h-full of-auto flex="~ col" gap-4 p7>
<SummaryCard
v-if="showSummary && lintResult?.meta.summary"
:summary="lintResult.meta.summary"
:total-issues="totalIssues"
:version="lintResult.meta.version"
:config="lintResult.logs.config"
:timestamp="lintResult.meta.timestamp"
/>

<Search v-model="search" />

<VisualEmptyState
v-if="showEmpty"
icon="i-twemoji:partying-face"
description="Congratulations! There is no oxlint issues."
/>

<template v-else>
<div v-if="showFiles" grid="~ cols-1 lg:cols-2" gap-4>
<FileCard v-for="file in filteredFiles" :key="file.filename" :file="file" />
</div>

<VisualEmptyState
v-else
icon="i-ph-file-minus-light"
description="No files found."
border="~ base rounded-lg dashed"
>
<button btn-action @click="resetSearch">Reset search</button>
</VisualEmptyState>
</template>
</div>
</div>
</template>
Loading
Loading