polen
Version:
A framework for delightful GraphQL developer portals
138 lines (118 loc) • 4.34 kB
text/typescript
import { Idx, Path } from '@wollybeard/kit'
import { type Route, type RouteFile, routeIsFromIndexFile, routeToPathExpression } from './route.js'
export type DiagnosticSeverity = `error` | `warning` | `info`
export type Diagnostic = DiagnosticIndexConflict | DiagnosticNumberedPrefixConflict | DiagnosticNumberedPrefixOnIndex
export interface DiagnosticIndexConflict {
type: `index-conflict`
severity: DiagnosticSeverity
message: string
literal: {
file: RouteFile
}
index: {
file: RouteFile
}
}
export interface DiagnosticNumberedPrefixConflict {
type: `numbered-prefix-conflict`
severity: DiagnosticSeverity
message: string
kept: {
file: RouteFile
order: number
}
dropped: {
file: RouteFile
order: number
}
}
export interface DiagnosticNumberedPrefixOnIndex {
type: `numbered-prefix-on-index`
severity: DiagnosticSeverity
message: string
file: RouteFile
order: number
}
export interface LintResult {
diagnostics: Diagnostic[]
routes: Route[]
}
export const lint = (routes: Route[]): LintResult => {
const diagnostics: Diagnostic[] = []
const seen = Idx.create({ key: routeToPathExpression })
// ━ Check for numbered prefix on index files
for (const route of routes) {
if (routeIsFromIndexFile(route) && route.logical.order !== undefined) {
const diagnostic: DiagnosticNumberedPrefixOnIndex = {
type: `numbered-prefix-on-index`,
severity: `warning`,
message: `Numbered prefix on index file has no effect. The file:\n ${
Path.format(route.file.path.relative)
}\n\nhas a numbered prefix (${route.logical.order}_) which doesn't affect ordering since index files represent their parent directory.`,
file: route.file,
order: route.logical.order,
}
diagnostics.push(diagnostic)
}
}
// ━ Check for conflicts
for (const route of routes) {
// Detect
const seenRoute = seen.get(route)
if (seenRoute) {
// Check if it's a numbered prefix conflict
if (seenRoute.logical.order !== undefined && route.logical.order !== undefined) {
// Handle numbered prefix conflict - keep the one with higher order
const [kept, dropped] = seenRoute.logical.order > route.logical.order ? [seenRoute, route] : [route, seenRoute]
if (dropped === seenRoute) {
seen.set(kept)
}
const orderMessage = kept.logical.order === dropped.logical.order
? `Both files have the same order number (${kept.logical.order}). The file processed later is being kept based on processing order.`
: `The file with lower order number (${dropped.logical.order}) is being dropped in favor of the one with higher order (${kept.logical.order}).`
const diagnostic: DiagnosticNumberedPrefixConflict = {
type: `numbered-prefix-conflict`,
severity: `error`,
// dprint-ignore
message: `Your files represent conflicting routes due to numbered prefixes. This file:\n ${Path.format(kept.file.path.relative)}\n\nconflicts with this file:\n\n ${Path.format(dropped.file.path.relative)}.\n\n${orderMessage}`,
kept: {
file: kept.file,
order: kept.logical.order!,
},
dropped: {
file: dropped.file,
order: dropped.logical.order!,
},
}
diagnostics.push(diagnostic)
continue
}
// Fix - ignore the index
const [index, literal] = routeIsFromIndexFile(route) ? [route, seenRoute] : [seenRoute, route]
if (seenRoute === index) {
seen.set(route)
}
// Report
const diagnostic: DiagnosticIndexConflict = {
type: `index-conflict`,
severity: `error`,
// dprint-ignore
message: `Your files represent conflicting routes. This index file route:\n ${Path.format(index.file.path.relative)}\n\nconflicts with this literal file route:\n\n ${Path.format(literal.file.path.relative)}.\n\nYour index route is being ignored.`,
literal: {
file: literal.file,
},
index: {
file: index.file,
},
}
diagnostics.push(diagnostic)
continue
}
// Continue
seen.set(route)
}
return {
diagnostics,
routes: seen.toArray(),
}
}