Skip to content
Merged
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
69 changes: 67 additions & 2 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://unpkg.com/oxlint/configuration_schema.json",
"plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"],
"jsPlugins": ["@e18e/eslint-plugin"],
"jsPlugins": ["@e18e/eslint-plugin", "eslint-plugin-regexp"],
"categories": {
"correctness": "error",
"suspicious": "warn",
Expand All @@ -17,7 +17,72 @@
"e18e/prefer-timer-args": "error",
"e18e/prefer-date-now": "error",
"e18e/prefer-regex-test": "error",
"e18e/prefer-array-some": "error"
"e18e/prefer-array-some": "error",
// RegExp - Possible Errors (most critical)
"regexp/no-contradiction-with-assertion": "error",
"regexp/no-dupe-disjunctions": "error",
"regexp/no-empty-alternative": "error",
"regexp/no-empty-capturing-group": "error",
"regexp/no-empty-character-class": "error",
"regexp/no-empty-group": "error",
"regexp/no-empty-lookarounds-assertion": "error",
"regexp/no-escape-backspace": "error",
"regexp/no-invalid-regexp": "error",
"regexp/no-lazy-ends": "error",
"regexp/no-misleading-capturing-group": "error",
"regexp/no-misleading-unicode-character": "error",
"regexp/no-missing-g-flag": "error",
"regexp/no-optional-assertion": "error",
"regexp/no-potentially-useless-backreference": "error",
"regexp/no-super-linear-backtracking": "error",
"regexp/no-useless-assertions": "error",
"regexp/no-useless-backreference": "error",
"regexp/no-useless-dollar-replacements": "error",
"regexp/strict": "error",
// RegExp - Best Practices
"regexp/confusing-quantifier": "warn",
"regexp/control-character-escape": "error",
"regexp/negation": "error",
"regexp/no-dupe-characters-character-class": "error",
"regexp/no-empty-string-literal": "error",
"regexp/no-extra-lookaround-assertions": "error",
"regexp/no-invisible-character": "error",
"regexp/no-legacy-features": "error",
"regexp/no-non-standard-flag": "error",
"regexp/no-obscure-range": "error",
"regexp/no-octal": "error",
"regexp/no-standalone-backslash": "error",
"regexp/no-trivially-nested-assertion": "error",
"regexp/no-trivially-nested-quantifier": "error",
"regexp/no-unused-capturing-group": "warn",
"regexp/no-useless-character-class": "error",
"regexp/no-useless-flag": "error",
"regexp/no-useless-lazy": "error",
"regexp/no-useless-quantifier": "error",
"regexp/no-useless-range": "error",
"regexp/no-useless-set-operand": "error",
"regexp/no-useless-string-literal": "error",
"regexp/no-useless-two-nums-quantifier": "error",
"regexp/no-zero-quantifier": "error",
"regexp/optimal-lookaround-quantifier": "warn",
"regexp/optimal-quantifier-concatenation": "error",
"regexp/prefer-predefined-assertion": "error",
"regexp/prefer-range": "error",
"regexp/prefer-set-operation": "error",
"regexp/simplify-set-operations": "error",
"regexp/use-ignore-case": "error",
// RegExp - Stylistic Issues (less critical, focused on consistency)
"regexp/match-any": "warn",
"regexp/no-useless-escape": "warn",
"regexp/no-useless-non-capturing-group": "warn",
"regexp/prefer-character-class": "warn",
"regexp/prefer-d": "warn",
"regexp/prefer-plus-quantifier": "warn",
"regexp/prefer-question-quantifier": "warn",
"regexp/prefer-star-quantifier": "warn",
"regexp/prefer-unicode-codepoint-escapes": "warn",
"regexp/prefer-w": "warn",
"regexp/sort-flags": "warn"
},
"overrides": [
{
Expand Down
2 changes: 1 addition & 1 deletion app/composables/useStructuredFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function parseSearchOperators(input: string): ParsedSearchOperators {

// Regex to match operators: name:value, desc:value, description:value, kw:value, keyword:value
// Value continues until whitespace or next operator
const operatorRegex = /\b(name|desc|description|kw|keyword):([^\s]+)/gi
const operatorRegex = /\b(name|desc|description|kw|keyword):(\S+)/gi

let remaining = input
let match
Expand Down
2 changes: 1 addition & 1 deletion app/pages/search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ function isValidNpmName(name: string): boolean {
// Must start with alphanumeric
if (!/^[a-z0-9]/i.test(name)) return false
// Can contain alphanumeric, hyphen, underscore
return /^[a-z0-9_-]+$/i.test(name)
return /^[\w-]+$/.test(name)
}

/** Validated user/org suggestion */
Expand Down
7 changes: 3 additions & 4 deletions app/utils/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ export function formatCompactNumber(

const fmt = (n: number) => {
if (decimals <= 0) return Math.round(n).toString()
return n
.toFixed(decimals)
.replace(/\.0+$/, '')
.replace(/(\.\d*?)0+$/, '$1')
const fixed = n.toFixed(decimals)
// Remove trailing zeros after decimal point
return fixed.includes('.') ? fixed.replace(/0+$/, '').replace(/\.$/, '') : fixed
}

const join = (suffix: string, n: number) => `${sign}${fmt(n)}${space ? ' ' : ''}${suffix}`
Expand Down
2 changes: 1 addition & 1 deletion app/utils/npm/outdated-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface OutdatedDependencyInfo {
*/
export function constraintIncludesPrerelease(constraint: string): boolean {
return (
/-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) ||
/-(?:alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) ||
/-\d/.test(constraint)
)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import validateNpmPackageName from 'validate-npm-package-name'

// Validation pattern for npm usernames/org names
// These follow similar rules: lowercase alphanumeric with hyphens, can't start/end with hyphen
const NPM_USERNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i
const NPM_USERNAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i

// ============================================================================
// Base Schemas
Expand Down
1 change: 1 addition & 0 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const config: KnipConfig = {

/** Oxlint plugins don't get picked up yet */
'@e18e/eslint-plugin',
'eslint-plugin-regexp',
],
ignoreUnresolved: ['#components', '#oauth/config'],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"@vitest/coverage-v8": "4.0.18",
"@vue/test-utils": "2.4.6",
"axe-core": "4.11.1",
"eslint-plugin-regexp": "3.0.0",
"fast-check": "4.5.3",
"h3": "1.15.5",
"knip": "5.83.0",
Expand Down
59 changes: 59 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/api/registry/org/[org]/packages.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CACHE_MAX_AGE_ONE_HOUR } from '#shared/utils/constants'
const NPM_REGISTRY = 'https://registry.npmjs.org'

// Validation pattern for npm org names (alphanumeric with hyphens)
const NPM_ORG_NAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i
const NPM_ORG_NAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i

function validateOrgName(name: string): void {
if (!name || name.length > 50 || !NPM_ORG_NAME_RE.test(name)) {
Expand Down
2 changes: 1 addition & 1 deletion server/api/registry/readme/[...pkg].get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const standardReadmeFilenames = [
]

/** Matches standard README filenames (case-insensitive, for checking registry metadata) */
const standardReadmePattern = /^readme(\.md|\.markdown)?$/i
const standardReadmePattern = /^readme(?:\.md|\.markdown)?$/i

/**
* Fetch README from jsdelivr CDN for a specific package version.
Expand Down
5 changes: 3 additions & 2 deletions server/utils/docs/text.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* oxlint-disable regexp/no-super-linear-backtracking */
/**
* Text Processing Utilities
*
Expand Down Expand Up @@ -54,7 +55,7 @@ export function cleanSymbolName(name: string): string {
* Create a URL-safe HTML anchor ID for a symbol.
*/
export function createSymbolId(kind: string, name: string): string {
return `${kind}-${name}`.replace(/[^a-zA-Z0-9-]/g, '_')
return `${kind}-${name}`.replace(/[^a-z0-9-]/gi, '_')
}

/**
Expand Down Expand Up @@ -126,7 +127,7 @@ export async function renderMarkdown(text: string, symbolLookup: SymbolLookup):
result = result
.replace(/`([^`]+)`/g, '<code class="docs-inline-code">$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
.replace(/\n\n+/g, '<br><br>')
.replace(/\n{2,}/g, '<br><br>')
.replace(/\n/g, '<br>')

// Highlight and restore code blocks
Expand Down
2 changes: 1 addition & 1 deletion server/utils/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export async function fetchLatestVersionWithFallback(name: string): Promise<stri
function constraintIncludesPrerelease(constraint: string): boolean {
// Look for prerelease identifiers in the constraint
return (
/-(alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) ||
/-(?:alpha|beta|rc|next|canary|dev|preview|pre|experimental)/i.test(constraint) ||
/-\d/.test(constraint)
) // e.g., -0, -1
}
Expand Down
2 changes: 1 addition & 1 deletion server/utils/shiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function highlightCodeSync(shiki: HighlighterCore, code: string, language
defaultColor: 'dark',
})
// Remove inline style from <pre> tag so CSS can control appearance
html = html.replace(/<pre([^>]*)\s+style="[^"]*"/, '<pre$1')
html = html.replace(/<pre([^>]*) style="[^"]*"/, '<pre$1')
// Shiki doesn't encode > in text content (e.g., arrow functions =>)
// We need to encode them for HTML validation
return escapeRawGt(html)
Expand Down
2 changes: 1 addition & 1 deletion shared/schemas/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const PackageNameSchema = v.pipe(
export const VersionSchema = v.pipe(
v.string(),
v.nonEmpty('Version is required'),
v.regex(/^[a-z0-9._+-]+$/i, 'Invalid version format'),
v.regex(/^[\w.+-]+$/, 'Invalid version format'),
)

/**
Expand Down
2 changes: 1 addition & 1 deletion shared/utils/git-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export function normalizeGitUrl(input: string): string | null {
const normalized = raw.replace(/^git\+/, '')

// Handle ssh:// and git:// URLs by converting to https://
if (/^(ssh|git):\/\//i.test(normalized)) {
if (/^(?:ssh|git):\/\//i.test(normalized)) {
try {
const url = new URL(normalized)
const path = url.pathname.replace(/^\/*/, '')
Expand Down
2 changes: 1 addition & 1 deletion shared/utils/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getLatestVersion } from 'fast-npm-meta'
import { createError } from 'h3'
import validatePackageName from 'validate-npm-package-name'

const NPM_USERNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i
const NPM_USERNAME_RE = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i
const NPM_USERNAME_MAX_LENGTH = 50

/**
Expand Down
2 changes: 1 addition & 1 deletion shared/utils/parse-basic-frontmatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function parseBasicFrontmatter(fileContent: string): Record<string, unknown> {
const match = fileContent.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/)
const match = fileContent.match(/^---[ \t]*\n([\s\S]*?)\n---[ \t]*(?:\n|$)/)
if (!match?.[1]) return {}

return match[1].split('\n').reduce<Record<string, unknown>>((acc, line) => {
Expand Down
Loading