Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/llms-txt-hub / packages/validators/src/validate-types.ts |
| Date | Not specified in source |
| Repository | Janady13/llms-txt-hub |
| Revision | e0f4bf978d4df168369c7601d9c5f5ea89e84377 |
| Branch | main |
| Path or archive member | packages/validators/src/validate-types.ts |
| Original SHA-256 | 4170e274792b5620b0e245c7e6cecb8428b488c24ad6509e800aa580bc48ffb4 |
| Git blob SHA | b2fe84eafaa82e19c056fdcc6469b429c4c8e31a |
| Reading-copy SHA-256 | 4170e274792b5620b0e245c7e6cecb8428b488c24ad6509e800aa580bc48ffb4 |
Recovered source transcript
Download complete source text ↓186 lines · 5.2 KB
#!/usr/bin/env tsx
/**
* Type Validator
*
* Ensures:
* - No 'any' types
* - Proper type exports
* - No implicit any
* - Consistent interface/type usage
*/
import fs from 'node:fs'
import chalk from 'chalk'
interface TypeViolation {
file: string
line: number
message: string
code: string
severity: 'error' | 'warning'
}
/**
* Validates TypeScript files for type safety issues like explicit any, missing annotations, and dangerous assertions
*/
class TypeValidator {
private violations: TypeViolation[] = []
validateFile(filePath: string): void {
const content = fs.readFileSync(filePath, 'utf-8')
const lines = content.split('\n')
lines.forEach((line, index) => {
this.checkExplicitAny(filePath, line, index + 1)
this.checkImplicitAny(filePath, line, index + 1)
this.checkTypeAssertions(filePath, line, index + 1)
this.checkUnusedTypes(filePath, line, index + 1)
})
}
private checkExplicitAny(file: string, line: string, lineNumber: number): void {
// Skip comments and strings
if (line.trim().startsWith('//') || line.trim().startsWith('*')) return
// Check for explicit 'any' usage
const anyPattern = /:\s*any(?:\s|$|[,)\]}])/
if (anyPattern.test(line)) {
// Allow in certain contexts
const asAny = ['as', 'any'].join(' ')
const allowedContexts = [
asAny, // Type assertions (though not ideal)
'// @ts-ignore',
'// eslint-disable',
'jest.fn()'
]
const isAllowed = allowedContexts.some(ctx => line.includes(ctx))
if (!isAllowed) {
this.violations.push({
file,
line: lineNumber,
message: 'Avoid using "any" type',
code: line.trim(),
severity: 'warning'
})
}
}
}
private checkImplicitAny(file: string, line: string, lineNumber: number): void {
// Check for function parameters without types
const functionPattern = /(?:function\s+\w+|const\s+\w+\s*=\s*(?:async\s*)?)\s*\(([^)]*)\)/
const match = line.match(functionPattern)
if (match?.[1]) {
const params = match[1].split(',').map(p => p.trim())
for (const param of params) {
if (param && !param.includes(':') && !param.includes('=') && param !== '') {
this.violations.push({
file,
line: lineNumber,
message: `Parameter "${param}" is missing type annotation`,
code: line.trim(),
severity: 'error'
})
}
}
}
}
private checkTypeAssertions(file: string, line: string, lineNumber: number): void {
// Check for dangerous type assertions
const doubleAssert = ['as', 'unknown', 'as'].join(' ')
if (line.includes(doubleAssert)) {
this.violations.push({
file,
line: lineNumber,
message: `Avoid double type assertions (${doubleAssert})`,
code: line.trim(),
severity: 'warning'
})
}
// Check for non-null assertions
if (line.match(/\w+!/)) {
this.violations.push({
file,
line: lineNumber,
message: 'Avoid non-null assertions (!), use proper null checks',
code: line.trim(),
severity: 'warning'
})
}
}
private checkUnusedTypes(file: string, line: string, lineNumber: number): void {
// Check for unused type imports
const typeImportPattern = /import\s+type\s*{([^}]+)}/
const match = line.match(typeImportPattern)
if (match) {
// This is a simple check - could be enhanced with AST parsing
const _types = match[1].split(',').map(t => t.trim())
// Note: This would need the full file content to check properly
// For now, just flag as info
}
}
report(): boolean {
if (this.violations.length === 0) {
console.log(chalk.green('✓ All type checks passed'))
return true
}
const errors = this.violations.filter(v => v.severity === 'error')
const warnings = this.violations.filter(v => v.severity === 'warning')
if (errors.length > 0) {
console.log(chalk.red(`\n✗ Found ${errors.length} type errors:`))
errors.forEach(v => {
console.log(chalk.red(` ${v.file}:${v.line} - ${v.message}`))
console.log(chalk.gray(` ${v.code}`))
})
}
if (warnings.length > 0) {
console.log(chalk.yellow(`\n⚠ Found ${warnings.length} type warnings:`))
warnings.forEach(v => {
console.log(chalk.yellow(` ${v.file}:${v.line} - ${v.message}`))
console.log(chalk.gray(` ${v.code}`))
})
}
return errors.length === 0
}
}
/**
* Validates an array of file paths for type safety violations and prints a report
*/
export function validateTypes(files: string[]): boolean {
const validator = new TypeValidator()
for (const file of files) {
if (!file.match(/\.(ts|tsx)$/)) continue
if (file.includes('node_modules')) continue
if (file.endsWith('.d.ts')) continue
try {
validator.validateFile(file)
} catch (error) {
console.error(chalk.red(`Error processing ${file}:`), error)
}
}
return validator.report()
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2)
const success = validateTypes(args)
process.exit(success ? 0 : 1)
}