From c97bd5606754439631ee4eb4599aa09397ba8693 Mon Sep 17 00:00:00 2001 From: Simek Date: Sat, 12 Sep 2026 15:13:04 +0200 Subject: [PATCH 1/2] add basic lint tools detection --- components/Icons/index.tsx | 20 +++++++ components/Library/MetaData.tsx | 15 +++++- scripts/fetch-github-data.ts | 2 + types/index.ts | 1 + util/github/hasFiles.ts | 95 +++++++++++++++++++++++++++------ util/strings.ts | 20 +++++++ 6 files changed, 136 insertions(+), 17 deletions(-) diff --git a/components/Icons/index.tsx b/components/Icons/index.tsx index 69ee5d65..bed48de0 100644 --- a/components/Icons/index.tsx +++ b/components/Icons/index.tsx @@ -1253,3 +1253,23 @@ export function SecurityIcon({ style }: IconProps) { ); } + +export function LintToolsIcon({ style }: IconProps) { + return ( + + + + + + + + + ); +} diff --git a/components/Library/MetaData.tsx b/components/Library/MetaData.tsx index 964f677c..87545f00 100644 --- a/components/Library/MetaData.tsx +++ b/components/Library/MetaData.tsx @@ -12,6 +12,7 @@ import { ForkIcon, IssueIcon, LicenseIcon, + LintToolsIcon, ModuleIcon, NativeCodeIcon, NightlyTestIcon, @@ -25,7 +26,7 @@ import { ConfigPluginContent } from '~/components/Library/ConfigPlugin'; import { Tooltip } from '~/components/Tooltip'; import { type LibraryDataEntryType, type LibraryType, type MetadataEntryType } from '~/types'; import { formatBytes } from '~/util/formatBytes'; -import { formatPackageManager, pluralize } from '~/util/strings'; +import { formatLintTools, formatPackageManager, pluralize } from '~/util/strings'; import tw from '~/util/tailwind'; import { DirectoryScore } from './DirectoryScore'; @@ -266,6 +267,16 @@ function generateSecondaryData(library: LibraryType, skipExamples: boolean): Met tooltip: 'Package manager', } : null, + skipExamples && (github?.lintTools?.length ?? 0) > 0 + ? { + id: 'lintTools', + icon: , + content: ( +

{github.lintTools?.map(formatLintTools).join(', ')}

+ ), + tooltip: 'Lint tools', + } + : null, ]; } @@ -291,7 +302,7 @@ function MetaData({ library, secondary, skipExamples = false }: Props) { ); return tooltip ? ( - + {tooltip} ) : ( diff --git a/scripts/fetch-github-data.ts b/scripts/fetch-github-data.ts index 93337eae..10f94dfe 100644 --- a/scripts/fetch-github-data.ts +++ b/scripts/fetch-github-data.ts @@ -4,6 +4,7 @@ import { type LibraryLicenseType, type LibraryType } from '~/types'; import detectModuleType from '~/util/github/detectModuleType'; import hasConfigPlugin from '~/util/github/hasConfigPlugin'; import { + detectLintStack, detectPackageManager, hasCCFile, hasChangelogFile, @@ -252,5 +253,6 @@ function createRepoDataWithResponse(json: any, monorepo: boolean): LibraryType[' json.packageManager ?? detectPackageManager(json.files) ?? detectPackageManager(json.rootFiles), + lintTools: detectLintStack(json.files) ?? detectLintStack(json.rootFiles), }; } diff --git a/types/index.ts b/types/index.ts index 08365383..f0338e87 100644 --- a/types/index.ts +++ b/types/index.ts @@ -101,6 +101,7 @@ export type LibraryType = LibraryDataEntryType & { configPlugin?: boolean; moduleType?: 'expo' | 'nitro' | 'turbo'; packageManager?: string; + lintTools?: string[]; urls: { repo: string; homepage?: string | null; diff --git a/util/github/hasFiles.ts b/util/github/hasFiles.ts index 28647cee..c4237446 100644 --- a/util/github/hasFiles.ts +++ b/util/github/hasFiles.ts @@ -2,41 +2,41 @@ import { type RepositoryTreeNode } from '~/types'; type RootFiles = { entries: RepositoryTreeNode[] } | null; -function hasMatchingFiles(rootFiles: RootFiles, fileNames: string[]) { +function hasMatchingFiles(rootFiles: RootFiles, fileNames: Set) { if (!rootFiles?.entries.length) { return false; } return rootFiles.entries.some( - node => fileNames.includes(node.name.toLowerCase()) && node.type === 'blob' + node => fileNames.has(node.name.toLowerCase()) && node.type === 'blob' ); } -const README_FILE_NAMES = ['readme', 'readme.md', 'readme.mdx']; +const README_FILE_NAMES = new Set(['readme', 'readme.md', 'readme.mdx']); export function hasReadmeFile(rootFiles: RootFiles) { return hasMatchingFiles(rootFiles, README_FILE_NAMES); } -const CONTRIBUTING_FILE_NAMES = ['contributing.md']; +const CONTRIBUTING_FILE_NAMES = new Set(['contributing.md']); export function hasContributingFile(rootFiles: RootFiles) { return hasMatchingFiles(rootFiles, CONTRIBUTING_FILE_NAMES); } -const CHANGELOG_FILE_NAMES = ['changelog.md']; +const CHANGELOG_FILE_NAMES = new Set(['changelog.md']); export function hasChangelogFile(rootFiles: RootFiles) { return hasMatchingFiles(rootFiles, CHANGELOG_FILE_NAMES); } -const CC_FILE_NAMES = ['code_of_conduct.md']; +const CC_FILE_NAMES = new Set(['code_of_conduct.md']); export function hasCCFile(rootFiles: RootFiles) { return hasMatchingFiles(rootFiles, CC_FILE_NAMES); } -const SECURITY_FILE_NAMES = ['security.md']; +const SECURITY_FILE_NAMES = new Set(['security.md']); export function hasSecurityFile(rootFiles: RootFiles) { return hasMatchingFiles(rootFiles, SECURITY_FILE_NAMES); @@ -44,24 +44,89 @@ export function hasSecurityFile(rootFiles: RootFiles) { export function detectPackageManager(rootFiles: RootFiles) { // LOCKS - if (hasMatchingFiles(rootFiles, ['bun.lock', 'bun.lockb'])) { + if (hasMatchingFiles(rootFiles, new Set(['bun.lock', 'bun.lockb']))) { return 'bun'; - } else if (hasMatchingFiles(rootFiles, ['pnpm-lock.yaml', 'shrinkwrap.yaml'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['pnpm-lock.yaml', 'shrinkwrap.yaml']))) { return 'pnpm'; - } else if (hasMatchingFiles(rootFiles, ['yarn.lock'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['yarn.lock']))) { return 'yarn'; - } else if (hasMatchingFiles(rootFiles, ['package-lock.json', 'shrinkwrap.json'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['package-lock.json', 'shrinkwrap.json']))) { return 'npm'; } // CONFIGS - if (hasMatchingFiles(rootFiles, ['bunfig.toml'])) { + if (hasMatchingFiles(rootFiles, new Set(['bunfig.toml']))) { return 'bun'; - } else if (hasMatchingFiles(rootFiles, ['.pnpm-workspace.yaml', '.pnpmfile.cjs'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['.pnpm-workspace.yaml', '.pnpmfile.cjs']))) { return 'pnpm'; - } else if (hasMatchingFiles(rootFiles, ['.yarnrc'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['.yarnrc']))) { return 'yarn@1'; - } else if (hasMatchingFiles(rootFiles, ['.yarnrc.yml'])) { + } else if (hasMatchingFiles(rootFiles, new Set(['.yarnrc.yml']))) { return 'yarn@2+'; } return undefined; } + +const OXLINT_CONFIG_FILE_NAMES = new Set([ + '.oxlintrc.json', + '.oxlintrc.jsonc', + 'oxlint.config.mts', + 'oxlint.config.ts', + 'oxlint.config.mjs', + 'oxlint.config.js', +]); +const OXFMT_CONFIG_FILE_NAMES = new Set([ + '.oxfmtrc.json', + '.oxfmtrc.jsonc', + 'oxfmt.config.mts', + 'oxfmt.config.ts', + 'oxfmt.config.mjs', + 'oxfmt.config.js', +]); +const ESLINT_CONFIG_FILE_NAMES = new Set([ + 'eslint.config.js', + 'eslint.config.mjs', + 'eslint.config.cjs', + 'eslint.config.ts', + 'eslint.config.mjs', + 'eslint.config.cjs', + '.eslintrc.js', + '.eslintrc.cjs', + '.eslintrc.json', + '.eslintrc.yaml', + '.eslintrc.yml', +]); +const PRETTIER_CONFIG_FILE_NAMES = new Set([ + '.prettierrc', + '.prettierrc.json', + '.prettierrc.json5', + '.prettierrc.yaml', + '.prettierrc.yml', + '.prettierrc.js', + '.prettierrc.cjs', + '.prettierrc.toml', + '.prettierrc.mjs', + 'prettier.config.js', + 'prettier.config.cjs', + 'prettier.config.mjs', +]); +const BIOME_CONFIG_FILE_NAMES = new Set(['biome.json', 'biome.jsonc']); + +export function detectLintStack(rootFiles: RootFiles) { + const lintTools = []; + if (hasMatchingFiles(rootFiles, OXLINT_CONFIG_FILE_NAMES)) { + lintTools.push('oxlint'); + } + if (hasMatchingFiles(rootFiles, OXFMT_CONFIG_FILE_NAMES)) { + lintTools.push('oxfmt'); + } + if (hasMatchingFiles(rootFiles, ESLINT_CONFIG_FILE_NAMES)) { + lintTools.push('eslint'); + } + if (hasMatchingFiles(rootFiles, PRETTIER_CONFIG_FILE_NAMES)) { + lintTools.push('prettier'); + } + if (hasMatchingFiles(rootFiles, BIOME_CONFIG_FILE_NAMES)) { + lintTools.push('biome'); + } + return lintTools; +} diff --git a/util/strings.ts b/util/strings.ts index 095b6d67..dae4dd3d 100644 --- a/util/strings.ts +++ b/util/strings.ts @@ -61,6 +61,26 @@ export function formatPackageManager(pmRaw?: string) { } } +export function formatLintTools(lintTool?: string) { + if (!lintTool) { + return undefined; + } + + if (lintTool === 'oxlint') { + return 'Oxlint'; + } else if (lintTool === 'oxfmt') { + return 'Oxfmt'; + } else if (lintTool === 'eslint') { + return 'ESLint'; + } else if (lintTool === 'prettier') { + return 'Prettier'; + } else if (lintTool === 'biome') { + return 'Biome'; + } else { + return lintTool; + } +} + export function childrenToText(children: ReactNode): string { return Children.toArray(children) .map(child => { From db17d911087c309379fd6df9b962807c4b8f8b2b Mon Sep 17 00:00:00 2001 From: Simek Date: Sat, 12 Sep 2026 15:17:24 +0200 Subject: [PATCH 2/2] simplify formatLintTools --- util/strings.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/util/strings.ts b/util/strings.ts index dae4dd3d..32573b2b 100644 --- a/util/strings.ts +++ b/util/strings.ts @@ -61,11 +61,7 @@ export function formatPackageManager(pmRaw?: string) { } } -export function formatLintTools(lintTool?: string) { - if (!lintTool) { - return undefined; - } - +export function formatLintTools(lintTool: string) { if (lintTool === 'oxlint') { return 'Oxlint'; } else if (lintTool === 'oxfmt') {