import * as ts from 'typescript'; const source = ` class main { f() { console.log('1') } main() { this.f() } } class second extends main { f() { console.log('2') } } `; const sourceFile = ts.createSourceFile('test.ts', source, ts.ScriptTarget.ESNext, true); const program = ts.createProgram({ rootNames: ['test.ts'], options: { noEmit: true }, host: { getSourceFile: (fileName) => (fileName === 'test.ts' ? sourceFile : undefined), getDefaultLibFileName: () => 'lib.d.ts', writeFile: () => {}, getCurrentDirectory: () => '', getDirectories: () => [], getCanonicalFileName: (fileName) => fileName, useCaseSensitiveFileNames: () => true, getNewLine: () => '\n', fileExists: (fileName) => fileName === 'test.ts', readFile: () => '', resolveModuleNames: () => [] }}); const checker = program.getTypeChecker(); function isMethodOverridden(callExpression: ts.CallExpression): boolean { const symbol = checker.getSymbolAtLocation(callExpression.expression); if (!symbol) return false; const declarations = symbol.getDeclarations(); if (!declarations) return false; for (const declaration of declarations) { const parentClass = declaration.parent; if (ts.isClassDeclaration(parentClass) && parentClass.name) { const parentClassName = parentClass.name.getText(); const parentClassSymbol = checker.getSymbolAtLocation(parentClass.name); if (parentClassSymbol) { const parentClassType = checker.getDeclaredTypeOfSymbol(parentClassSymbol); const derivedClasses = getAllDerivedClasses(parentClassType); for (const derivedClass of derivedClasses) { const derivedClassMembers = derivedClass.getProperties(); for (const member of derivedClassMembers) { if (member.name === symbol.getName() && member.declarations[0].parent.kind === ts.SyntaxKind.ClassDeclaration) { return true; } } } } } } return false; } function getAllDerivedClasses(baseType: ts.Type): ts.Type[] { const derivedClasses: ts.Type[] = []; const visitedClasses = new Set(); function visitDerivedClasses(type: ts.Type) { if (visitedClasses.has(type)) { return; } visitedClasses.add(type); const subtypes = (checker as any).getImmediateDerivedClasses(type); for (const subtype of subtypes) { derivedClasses.push(subtype); visitDerivedClasses(subtype); } } visitDerivedClasses(baseType); return derivedClasses; } function findCallExpressions(node: ts.Node) { if (ts.isCallExpression(node)) { const overridden = isMethodOverridden(node); console.log(`Method ${node.expression.getText()} is ${overridden ? '' : 'not '}overridden.`); } ts.forEachChild(node, findCallExpressions); } function traverseAst(node: ts.Node) { if (ts.isClassDeclaration(node)) { ts.forEachChild(node, findCallExpressions); } else { ts.forEachChild(node, traverseAst); } } traverseAst(sourceFile);