WIP: codemod has more stable detection of client expressions

This commit is contained in:
Josh Mock
2025-01-10 14:45:42 -06:00
parent 444975b4e6
commit 70aae3b44f

View File

@ -20,19 +20,46 @@ import ts from 'typescript'
import path from 'node:path'
import minimist from 'minimist'
const clientProps = ['diagnostic', 'connectionPool', 'transport', 'serializer', 'child', 'close']
/**
* Returns true if the interface type is a `Client`, otherwise false
* @remarks Uses `clientProps` as a duck-typing way to identify `Client` instances. There's probably a better way, but this is probably accurate enough.
*/
function classIsClient(type: ts.InterfaceType) {
const typeProps = type.getProperties().map(prop => prop.escapedName.toString())
const apis = [
'asyncSearch',
'autoscaling',
'bulk',
'capabilities',
'cat',
'ccr',
'clearScroll',
'closePointInTime',
'cluster',
'connector',
'count',
'create',
'danglingIndices',
'delete',
'deleteByQuery',
'deleteByQueryRethrottle',
'deleteScript',
'enrich',
'eql',
'esql',
'exists',
'existsSource',
'explain',
'features',
'fieldCaps',
]
let hasClientProps = true
clientProps.forEach(prop => {
if (!typeProps.includes(prop)) hasClientProps = false
})
return hasClientProps
/**
* Detects whether a node is a `Client` instance identifier
* @remarks Uses duck-typing by checking that several Elasticsearch APIs exist as members on the identifier
*/
function isClient(node: ts.Identifier) {
const type = checker.getTypeAtLocation(node)
const properties = type.getProperties().map(prop => prop.escapedName.toString())
for (const api of apis) {
if (!properties.includes(api)) return false
}
return true
}
/**
@ -41,11 +68,9 @@ function classIsClient(type: ts.InterfaceType) {
function isClientExpression(node: ts.CallExpression): boolean {
let flag = false
function visitIdentifiers(node: ts.Node) {
if (ts.isIdentifier(node)) {
const type = checker.getTypeAtLocation(node)
if (type.isClass() && classIsClient(type)) {
flag = true
}
if (ts.isIdentifier(node) && isClient(node)) {
flag = true
return
}
ts.forEachChild(node, visitIdentifiers)
}
@ -76,26 +101,6 @@ function collectClientCallExpressions(node: ts.SourceFile): ts.CallExpression[]
return clientExpressions
}
// function getObjectLiteralExpression(node: ts.Node) {
// if (ts.isObjectLiteralExpression(node)) {
// return node
// } else if (ts.isIdentifier(node)) {
// // traverse up to assignment
// // @ts-expect-error
// let antecedent = node.flowNode.antecedent
// let parentNode = null
// while (true) {
// if (antecedent.node != null) {
// parentNode = antecedent.node
// antecedent = antecedent.antecedent
// } else {
// break
// }
// }
// return parentNode
// }
// }
function fixBodyProp(sourceFile: ts.SourceFile, node: ts.Node) {
if (ts.isObjectLiteralExpression(node)) {
// @ts-expect-error need to cast `prop` to a more specific type
@ -141,24 +146,27 @@ function lookForBodyProp(sourceFile: ts.SourceFile, node: ts.CallExpression) {
// build TS project from provided file names
const args = minimist(process.argv.slice(2))
const cwd = process.cwd()
const files = args._.map(filename => path.join(cwd, filename))
const files = args._.map(file => path.join(cwd, file))
const program = ts.createProgram(files, {})
const checker = program.getTypeChecker()
let processed = 0
program.getSourceFiles().forEach(sourceFile => {
if (sourceFile.fileName.includes('/node_modules/')) return
if (program.isSourceFileFromExternalLibrary(sourceFile)) return
const { fileName } = sourceFile
try {
// get all `Client` call expressions
const exprs = collectClientCallExpressions(sourceFile)
console.log(`found ${exprs.length} expressions in ${sourceFile.fileName}`)
if (exprs.length > 0) {
console.log(`found ${exprs.length} Client expressions in ${fileName}`)
}
// for each call expression, get the first function argument, determine if it's an object and whether it has a `body` key
exprs.forEach(expr => lookForBodyProp(sourceFile, expr))
} catch (e) {
// continue
console.error(`Could not process ${sourceFile.fileName}: ${e}`)
console.error(`Could not process ${fileName}: ${e}`)
}
processed++
})
console.log(`Done processing ${processed} files`)
console.log(`Done scanning ${processed} files`)