Skip to content

Commit

Permalink
feat: improve files globs matching display
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 5, 2024
1 parent 2595b46 commit fd35b20
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 39 deletions.
2 changes: 1 addition & 1 deletion app/components/ConfigItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const extraConfigs = computed(() => {
<div flex="~ col gap-2">
<div>Applies to files matching</div>
<div flex="~ gap-2 items-center wrap">
<GlobItem v-for="glob, idx of config.files" :key="idx" :glob="glob" />
<GlobItem v-for="glob, idx of config.files?.flat()" :key="idx" :glob="glob" />
</div>
</div>
</div>
Expand Down
30 changes: 22 additions & 8 deletions app/components/FileGroupItem.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { defineModel, ref, watchEffect } from 'vue'
import { payload } from '../composables/payload'
import { useRouter } from '#app/composables/router'
import type { FilesGroup } from '~~/types'
Expand Down Expand Up @@ -82,18 +81,19 @@ function goToConfig(idx: number) {

<div flex="~ gap-2 items-center">
<div i-ph-stack-duotone flex-none />
<div>Configs specific to files ({{ group.configs.length }})</div>
<div>Configs Specific to the Files ({{ group.configs.length }})</div>
</div>

<div flex="~ col gap-1" ml6 mt--2>
<div v-for="configIdx of group.configs" :key="configIdx" font-mono flex="~ gap-2">
<div v-for="config, idx of group.configs" :key="idx" font-mono flex="~ gap-2">
<VDropdown>
<button border="~ base rounded px2" flex="~ gap-2 items-center" hover="bg-active" px2 py0.5>
<ColorizedConfigName v-if="payload.configs[configIdx].name" :name="payload.configs[configIdx].name!" />
<button border="~ base rounded px2" flex="~ gap-2 items-center" hover="bg-active" px2>
<ColorizedConfigName v-if="config.name" :name="config.name!" />
<div v-else op50 italic>
anonymous
</div>
<div op50 text-sm>
#{{ configIdx + 1 }}
#{{ idx + 1 }}
</div>
</button>
<template #popper="{ shown }">
Expand All @@ -102,7 +102,7 @@ function goToConfig(idx: number) {
<button
action-button
title="Copy"
@click="goToConfig(configIdx)"
@click="goToConfig(idx)"
>
<div i-ph-stack-duotone />
Go to this config
Expand All @@ -117,7 +117,12 @@ function goToConfig(idx: number) {
Applies to files matching
</div>
<div flex="~ gap-2 items-center wrap">
<GlobItem v-for="glob, idx of payload.configs[configIdx].files" :key="idx" :glob="glob" />
<GlobItem
v-for="glob, idx2 of config.files?.flat()"
:key="idx2"
:glob="glob"
:active="group.globs.has(glob)"
/>
</div>
</div>
</div>
Expand All @@ -127,6 +132,15 @@ function goToConfig(idx: number) {
</VDropdown>
</div>
</div>
<div flex="~ gap-2 items-center">
<div i-ph-file-magnifying-glass-duotone flex-none />
<div>Matched Globs</div>
</div>
<div flex="~ gap-1 wrap" ml6 mt--2>
<GlobItem v-for="glob, idx2 of group.globs" :key="idx2" :glob="glob" />
</div>
</div>
</details>
</template>
15 changes: 11 additions & 4 deletions app/components/GlobItem.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
<script setup lang="ts">
import type { Linter } from 'eslint'
defineProps<{
glob: Linter.FlatConfigFileSpec | Linter.FlatConfigFileSpec[]
}>()
withDefaults(
defineProps<{
glob: Linter.FlatConfigFileSpec
active?: boolean | null
}>(),
{ active: null },
)
</script>

<template>
<div border="~ base rounded" bg-gray:5 px2 font-mono op75>
<div
border="~ rounded" px2 font-mono
:class="active === true ? 'border-amber:50 text-amber bg-amber:5' : active === false ? 'border-base bg-gray:5 text-gray op50' : 'border-base bg-gray:5 text-gray'"
>
{{ glob }}
</div>
</template>
2 changes: 1 addition & 1 deletion app/components/RuleStateItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function goto() {
Applies to files matching
</div>
<div flex="~ gap-2 items-center wrap">
<GlobItem v-for="glob, idx of config.files" :key="idx" :glob="glob" />
<GlobItem v-for="glob, idx of config.files?.flat()" :key="idx" :glob="glob" />
</div>
</div>
</template>
Expand Down
43 changes: 32 additions & 11 deletions app/composables/configs.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
import type { Linter } from 'eslint'
import { minimatch } from 'minimatch'
import type { FlatESLintConfigItem } from '~~/types'
import type { FileConfigMatchResult, FlatESLintConfigItem } from '~~/types'

function matchGlob(file: string, glob: (Linter.FlatConfigFileSpec | Linter.FlatConfigFileSpec[])[]) {
function getMatchedGlobs(file: string, glob: (Linter.FlatConfigFileSpec | Linter.FlatConfigFileSpec[])[]) {
const globs = (Array.isArray(glob) ? glob : [glob]).flat()
return globs.some(glob => typeof glob === 'function' ? glob(file) : minimatch(file, glob))
return globs.filter(glob => typeof glob === 'function' ? glob(file) : minimatch(file, glob)).flat()
}

function matchGlob(file: string, glob: (Linter.FlatConfigFileSpec | Linter.FlatConfigFileSpec[])[]) {
return getMatchedGlobs(file, glob).length > 0
}

export function isIgnoreOnlyConfig(config: FlatESLintConfigItem) {
const keys = Object.keys(config).filter(i => !['name'].includes(i))
return keys.length === 1 && keys[0] === 'ignores'
}

export function getMatchedConfigs(filepath: string, configs: FlatESLintConfigItem[]): number[] {
export function getMatchedConfigs(filepath: string, configs: FlatESLintConfigItem[]): FileConfigMatchResult[] {
const ignoreOnlyConfigs = configs.filter(isIgnoreOnlyConfig)
const isIgnored = ignoreOnlyConfigs.some(config => matchGlob(filepath, config.ignores!))
if (isIgnored)
return []

const isAnyIncluded = configs.some(config => matchGlob(filepath, config.files || []))
if (!isAnyIncluded)
return []

return configs
.map((config, index) => {
const isIncluded = config.files ? matchGlob(filepath, config.files) : true
const isExcluded = config.ignores ? matchGlob(filepath, config.ignores) : false
if (isIncluded && !isExcluded)
return index
return undefined
.map((config, index): FileConfigMatchResult | undefined => {
if (config.ignores && matchGlob(filepath, config.ignores))
return undefined
if (!config.files) {
return {
config,
index,
globs: [],
}
}
const includeGlob = getMatchedGlobs(filepath, config.files)
if (!includeGlob.length)
return undefined
return {
config,
index,
globs: includeGlob,
}
})
.filter(index => index !== undefined) as number[]
.filter(notNullish)
}

function notNullish<T>(value: T): value is NonNullable<T> {
return value != null
}
28 changes: 20 additions & 8 deletions app/composables/payload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { $fetch } from 'ofetch'
import type { ErrorInfo, FilesGroup, Payload, ResolvedPayload, RuleConfigStates, RuleInfo } from '~~/types'
import type { ErrorInfo, FileConfigMatchResult, FilesGroup, Payload, ResolvedPayload, RuleConfigStates, RuleInfo } from '~~/types'

const LOG_NAME = '[ESLint Config Inspector]'

Expand Down Expand Up @@ -96,18 +96,30 @@ export function resolvePayload(payload: Payload): ResolvedPayload {
.map((config, idx) => (!config.files && !config.ignores) || isIgnoreOnlyConfig(config) ? idx : undefined)
.filter((idx): idx is number => idx !== undefined)

const filesMatchedConfigsMap = new Map<string, number[]>()
const filesMatchedConfigsMap = new Map<string, FileConfigMatchResult[]>()
payload.files.forEach((file) => {
filesMatchedConfigsMap.set(file, getMatchedConfigs(file, payload.configs))
})

const filesGroupMap = new Map<string, FilesGroup>()
for (const [file, configs] of filesMatchedConfigsMap.entries()) {
const configIndex = configs.sort((a, b) => a - b).filter(i => !generalConfigs.includes(i))
const id = configIndex.join(',')
if (!filesGroupMap.has(id))
filesGroupMap.set(id, { id, files: [], configs: configIndex })
filesGroupMap.get(id)!.files.push(file)
for (const [file, values] of filesMatchedConfigsMap.entries()) {
const configs = values.sort((a, b) => a.index - b.index).filter(i => !generalConfigs.includes(i.index))
const id = configs.map(i => i.index).join('-')
if (!filesGroupMap.has(id)) {
filesGroupMap.set(id, {
id,
files: [],
configs: configs.map(i => payload.configs[i.index]),
globs: new Set(),
})
}
const item = filesGroupMap.get(id)!
item.files.push(file)
values.forEach(i =>
i.globs.forEach(glob =>
item.globs.add(glob),
),
)
}

configsOpenState.value = payload.configs.length >= 10
Expand Down
6 changes: 2 additions & 4 deletions app/pages/configs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ function collapseAll() {
const filteredConfigs = computed(() => {
let configs = payload.value.configs
if (filters.filepath) {
const indexes = getMatchedConfigs(filters.filepath, configs)
configs = configs.filter((_, idx) => indexes.includes(idx))
}
if (filters.filepath)
configs = getMatchedConfigs(filters.filepath, configs).map(i => i.config)
if (filters.rule)
configs = configs.filter(config => filters.rule! in (config.rules || {}))
Expand Down
11 changes: 9 additions & 2 deletions types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ export interface ErrorInfo {
export interface FilesGroup {
id: string
files: string[]
configs: number[]
configs: FlatESLintConfigItem[]
globs: Set<Linter.FlatConfigFileSpec>
}

export interface FileConfigMatchResult {
config: FlatESLintConfigItem
index: number
globs: Linter.FlatConfigFileSpec[]
}

export interface ResolvedPayload extends Payload {
ruleStateMap: Map<string, RuleConfigStates>
filesMatchedConfigsMap: Map<string, number[]>
filesMatchedConfigsMap: Map<string, FileConfigMatchResult[]>
filesGroup: FilesGroup[]
}

Expand Down

0 comments on commit fd35b20

Please sign in to comment.