mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat: add class and function detectors for TypeScript/TSX source files
This commit is contained in:
64
detection/detectors/class-detector.ts
Normal file
64
detection/detectors/class-detector.ts
Normal 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
|
||||
}
|
||||
78
detection/detectors/function-detector.ts
Normal file
78
detection/detectors/function-detector.ts
Normal 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
45
detection/index.ts
Normal 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))
|
||||
Reference in New Issue
Block a user