feat: add class and function detectors for TypeScript/TSX source files

This commit is contained in:
2025-12-27 19:15:25 +00:00
parent 25228f3371
commit d152f822b3
3 changed files with 187 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import * as ts from 'typescript'
import { Detector, DetectionFinding, DetectorContext } from '..'
const getLocation = (sourceFile: ts.SourceFile, node: ts.Node) => {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
return {
line: line + 1,
column: character + 1
}
}
const getClassName = (
node: ts.ClassDeclaration | ts.ClassExpression,
sourceFile: ts.SourceFile
): string => {
if (node.name) {
return node.name.getText(sourceFile)
}
const parent = node.parent
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
return parent.name.text
}
return 'anonymous'
}
const collectClasses = (context: DetectorContext): DetectionFinding[] => {
const sourceFile = ts.createSourceFile(
context.filePath,
context.source,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX
)
const findings: DetectionFinding[] = []
const visit = (node: ts.Node) => {
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
const name = getClassName(node, sourceFile)
findings.push({
detectorId: 'class-detector',
name,
message: `Class detected: ${name}`,
location: getLocation(sourceFile, node)
})
}
ts.forEachChild(node, visit)
}
visit(sourceFile)
return findings
}
export const classDetector: Detector = {
id: 'class-detector',
description: 'Detects class declarations and expressions within a TypeScript/TSX source file.',
detect: collectClasses
}

View File

@@ -0,0 +1,78 @@
import * as ts from 'typescript'
import { Detector, DetectionFinding, DetectorContext } from '..'
type FunctionLike =
| ts.FunctionDeclaration
| ts.FunctionExpression
| ts.ArrowFunction
| ts.MethodDeclaration
| ts.ConstructorDeclaration
const getLocation = (sourceFile: ts.SourceFile, node: ts.Node) => {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
return {
line: line + 1,
column: character + 1
}
}
const getFunctionName = (node: FunctionLike, sourceFile: ts.SourceFile): string => {
if ('name' in node && node.name) {
return node.name.getText(sourceFile)
}
const parent = node.parent
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
return parent.name.text
}
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
return parent.name.text
}
return 'anonymous'
}
const collectFunctions = (context: DetectorContext): DetectionFinding[] => {
const sourceFile = ts.createSourceFile(
context.filePath,
context.source,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX
)
const findings: DetectionFinding[] = []
const visit = (node: ts.Node) => {
if (
ts.isFunctionDeclaration(node) ||
ts.isFunctionExpression(node) ||
ts.isArrowFunction(node) ||
ts.isMethodDeclaration(node) ||
ts.isConstructorDeclaration(node)
) {
const name = getFunctionName(node, sourceFile)
findings.push({
detectorId: 'function-detector',
name,
message: `Function detected: ${name}`,
location: getLocation(sourceFile, node)
})
}
ts.forEachChild(node, visit)
}
visit(sourceFile)
return findings
}
export const functionDetector: Detector = {
id: 'function-detector',
description: 'Detects functions and methods within a TypeScript/TSX source file.',
detect: collectFunctions
}

45
detection/index.ts Normal file
View File

@@ -0,0 +1,45 @@
import { classDetector } from './detectors/class-detector'
import { functionDetector } from './detectors/function-detector'
export type DetectorContext = {
filePath: string
source: string
}
export type DetectionFinding = {
detectorId: string
name: string
message: string
location?: {
line: number
column: number
}
}
export interface Detector {
id: string
description: string
detect: (context: DetectorContext) => DetectionFinding[]
}
export class DetectorRegistry {
private readonly detectors: Detector[] = []
register(detector: Detector): void {
this.detectors.push(detector)
}
list(): Detector[] {
return [...this.detectors]
}
run(context: DetectorContext): DetectionFinding[] {
return this.detectors.flatMap((detector) => detector.detect(context))
}
}
export const registry = new DetectorRegistry()
const builtInDetectors: Detector[] = [functionDetector, classDetector]
builtInDetectors.forEach((detector) => registry.register(detector))