Last active
August 5, 2019 07:29
-
-
Save kgrz/fe9c62c43601098ac1862b65dc687bb5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// wip | |
import * as ts from 'typescript'; | |
import { readFileSync, stat } from 'fs'; | |
import { promisify } from 'util'; | |
const statp = promisify(stat); | |
const filelist = process.argv; | |
if (!filelist || !filelist.slice(2).length) { | |
console.error('need atleast one file.'); | |
process.exit(255); | |
} | |
type Ctx = { | |
keys: Array<string>; | |
filename: string; | |
}; | |
const traverseFnBody = (body: ts.Block | ts.ConciseBody, ctx: Ctx) => { | |
function traverseReturnStatementExpression(node?: ts.Node) { | |
if (!node) return; | |
// TODO: handle the cases where shorthand property assignment and | |
// object spread operations are used. In those cases the | |
// node.properties[0].name won't be available. | |
if (ts.isObjectLiteralExpression(node)) { | |
const propsToList = node.properties | |
.map(prop => { | |
if (prop.name && 'text' in prop.name) { | |
return prop.name.text; | |
} else { | |
console.error('computed property: ', prop); | |
} | |
}) | |
.join(','); | |
console.log(propsToList); | |
ctx.keys.push(propsToList); | |
} | |
ts.forEachChild(node, traverseReturnStatementExpression); | |
} | |
function traverseNode(node: ts.Node) { | |
if (ts.isReturnStatement(node)) { | |
traverseReturnStatementExpression(node.expression); | |
} | |
ts.forEachChild(node, traverseNode); | |
} | |
traverseNode(body); | |
}; | |
const functionLabel = 'mapStateToProps'; | |
const traverse = (sourceFile: ts.SourceFile, ctx: Ctx) => { | |
traverseNode(sourceFile); | |
function traverseNode(node: ts.Node) { | |
let body: ts.Block | ts.ConciseBody | undefined; | |
if (ts.isVariableDeclaration(node)) { | |
if (node.name && ts.isIdentifier(node.name) && node.name.text === functionLabel) { | |
if (node.initializer && (ts.isFunctionExpression(node.initializer) || ts.isArrowFunction(node.initializer))) { | |
body = node.initializer.body; | |
} | |
} | |
} | |
if (ts.isFunctionDeclaration(node)) { | |
if (node.name && ts.isIdentifier(node.name) && node.name.text === functionLabel) { | |
body = node.body; | |
} | |
} | |
if (body) { | |
traverseFnBody(body, ctx); | |
} | |
ts.forEachChild(node, traverseNode); | |
} | |
}; | |
const promises = filelist.slice(2).map(filename => { | |
return statp(filename).then((stats: any) => { | |
if (stats.isDirectory()) return; | |
const source = ts.createSourceFile(filename, readFileSync(filename).toString(), ts.ScriptTarget.ES2015, true); | |
const ctx: Ctx = { filename, keys: [] }; | |
traverse(source, ctx); | |
return ctx; | |
}); | |
}); | |
Promise.all(promises).then(list => console.log(JSON.stringify(list))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ts from 'typescript'; | |
import { readFileSync, stat } from 'fs'; | |
import { promisify } from 'util'; | |
const statp = promisify(stat); | |
const filelist = process.argv; | |
if (!filelist || !filelist.slice(2).length) { | |
console.error('need atleast one file.'); | |
process.exit(255); | |
} | |
const caseLabel = (node: ts.CaseClause) => { | |
let labels: string[] = []; | |
function traverseAccessNode(node: ts.PropertyAccessExpression | ts.ElementAccessExpression) { | |
const { expression } = node; | |
if (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) { | |
traverseAccessNode(expression); | |
} else if (ts.isIdentifier(expression)) { | |
labels.push(expression.text); | |
} | |
if (ts.isPropertyAccessExpression(node)) { | |
const { name } = node; | |
// this check probably is not required at all. | |
if (ts.isIdentifier(name)) { | |
labels.push(name.text); | |
} | |
} else { | |
const { argumentExpression } = node; | |
if ('text' in argumentExpression) { | |
labels.push(argumentExpression['text']); | |
} | |
} | |
} | |
if (ts.isIdentifier(node.expression)) { | |
labels.push(node.expression.text); | |
} else if (ts.isPropertyAccessExpression(node.expression)) { | |
traverseAccessNode(node.expression); | |
} | |
return labels.join('.'); | |
}; | |
const AsyncAction = /(REQUEST|SUCCESS|ERROR)$/; | |
let asyncClauses = 0; | |
const thresholds = [3]; | |
// TODO: Handle two reducers in a single file. Right now this function counts | |
// the number of case clauses inside each file, which could be higher if there | |
// are two simple reducers (3 case clauses) in a single file. | |
// | |
// TODO: Handle async-only reducers that have multiple case clauses. This might | |
// mean there are more than 3 case keywords, but the actual blocks associated | |
// would just switch on the SUCCESS, LOADING and ERROR enums. | |
const traverse = (sourceFile: ts.SourceFile, filename: string) => { | |
let caseClauses = 0; | |
let keys: Array<string> = []; | |
traverseNode(sourceFile); | |
function traverseNode(node: ts.Node) { | |
if (ts.isCaseClause(node)) { | |
caseClauses++; | |
const identifier = caseLabel(node); | |
if (identifier) keys.push(identifier); | |
} | |
ts.forEachChild(node, traverseNode); | |
} | |
if (thresholds.indexOf(caseClauses) > -1) { | |
console.log(`${caseClauses} case clauses in ${filename}`); | |
asyncClauses += keys.filter(key => AsyncAction.test(key)).length; | |
console.log('async clauses: ', asyncClauses); | |
} | |
}; | |
filelist.slice(2).forEach(filename => { | |
statp(filename).then(stats => { | |
if (stats.isDirectory()) return; | |
const source = ts.createSourceFile(filename, readFileSync(filename).toString(), ts.ScriptTarget.ES2015, true); | |
traverse(source, filename); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment