Filter out strings and numbers

This commit is contained in:
Jerko Steiner 2019-08-12 14:14:50 +07:00
parent 3ba279a0ab
commit d1072031d6

View File

@ -9,6 +9,27 @@ function isTypeReference(type: ts.ObjectType): type is ts.TypeReference {
return !!(type.objectFlags & ts.ObjectFlags.Reference) return !!(type.objectFlags & ts.ObjectFlags.Reference)
} }
function filterInvisibleProperties(type: ts.Symbol): boolean {
const flags = ts.getCombinedModifierFlags(type.valueDeclaration)
return !(flags & ts.ModifierFlags.NonPublicAccessibilityModifier)
}
interface IClassProperty {
name: string
type: ts.Type
relevantTypes: ts.Type[]
typeString: string
optional: boolean
}
interface IClassDefinition {
name: string
typeParameters: ts.TypeParameter[]
relevantTypeParameters: ts.Type[]
allRelevantTypes: ts.Type[]
properties: IClassProperty[]
}
export function typecheck() { export function typecheck() {
/** Generate interfaces for all exported classes in a set of .ts files */ /** Generate interfaces for all exported classes in a set of .ts files */
function generateInterfaces( function generateInterfaces(
@ -21,33 +42,62 @@ export function typecheck() {
// Get the checker, we will use it to find more about classes // Get the checker, we will use it to find more about classes
const checker = program.getTypeChecker() const checker = program.getTypeChecker()
// Visit every sourceFile in the program const classDefs: IClassDefinition[] = []
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
// Walk the tree to search for classes
ts.forEachChild(sourceFile, visit)
}
}
return
function typeToString(type: ts.Type): string { function typeToString(type: ts.Type): string {
return checker.typeToString(type) return checker.typeToString(type)
} }
/**
* Can be used to filters out global types like Array or string from a list
* of types. For example: types.filter(filterGlobalTypes)
*/
function filterGlobalTypes(type: ts.Type): boolean { function filterGlobalTypes(type: ts.Type): boolean {
console.log('filterGlobalTypes', typeToString(type))
const symbol = type.getSymbol() const symbol = type.getSymbol()
if (!symbol) {
console.log(' no symbol')
return false
}
if (symbol && !((symbol as any).parent)) { if (symbol && !((symbol as any).parent)) {
console.log(' no parent')
// e.g. Array symbol has no parent // e.g. Array symbol has no parent
return false return false
} }
if (type.isLiteral()) {
console.log(' is literal')
return false
}
if (type.isUnionOrIntersection()) { if (type.isUnionOrIntersection()) {
console.log(' is union or intersection')
// union type params should have already been extracted // union type params should have already been extracted
return false return false
} }
if (isObjectType(type) && isTypeReference(type)) {
console.log(' is reference')
if (isObjectType(type.target)
&& type.target.objectFlags & ts.ObjectFlags.Tuple) {
return false
}
}
// if (isObjectType(type) && type.objectFlags & ts.ObjectFlags.Tuple) {
// console.log(' is tuple')
// // tuple params should have already been extracted
// return false
// }
console.log(' ',
type.flags,
(type as any).objectFlags,
!!symbol,
symbol && !!(symbol as any).parent,
)
return true return true
} }
/**
* Converts a generic type to the target of the type reference.
*/
function mapGenericTypes(type: ts.Type): ts.Type { function mapGenericTypes(type: ts.Type): ts.Type {
if (isObjectType(type) && isTypeReference(type)) { if (isObjectType(type) && isTypeReference(type)) {
return type.target return type.target
@ -55,11 +105,17 @@ export function typecheck() {
return type return type
} }
/**
* Removes duplicates from an array of types
*/
function filterDuplicates(type: ts.Type, i: number, arr: ts.Type[]) { function filterDuplicates(type: ts.Type, i: number, arr: ts.Type[]) {
// TODO improve performance of this method // TODO improve performance of this method
return i === arr.indexOf(type) return i === arr.indexOf(type)
} }
/**
* Recursively retrieves a list of all type parameters.
*/
function getAllTypeParameters(type: ts.Type): ts.Type[] { function getAllTypeParameters(type: ts.Type): ts.Type[] {
if (isObjectType(type) && isTypeReference(type)) { if (isObjectType(type) && isTypeReference(type)) {
const types: ts.Type[] = [type] const types: ts.Type[] = [type]
@ -99,7 +155,20 @@ export function typecheck() {
return [type] return [type]
} }
/** visit nodes finding exported classes */ /**
* True if this is visible outside this file, false otherwise
*/
function isNodeExported(node: ts.Node): boolean {
return (
(ts.getCombinedModifierFlags(node as any) &
ts.ModifierFlags.Export) !== 0 ||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
)
}
/**
* Visit nodes finding exported classes
*/
function visit(node: ts.Node) { function visit(node: ts.Node) {
// Only consider exported nodes // Only consider exported nodes
if (!isNodeExported(node)) { if (!isNodeExported(node)) {
@ -110,74 +179,102 @@ export function typecheck() {
// This is a top level class, get its symbol // This is a top level class, get its symbol
const symbol = checker.getSymbolAtLocation(node.name) const symbol = checker.getSymbolAtLocation(node.name)
const typeParameters: ts.Type[] = [] const typeParameters: ts.TypeParameter[] = []
const expandedTypeParameters: ts.Type[] = []
const allRelevantTypes: ts.Type[] = []
if (symbol) { if (symbol) {
console.log('===') // console.log('===')
// console.log('text', node.getText(node.getSourceFile())) // console.log('text', node.getText(node.getSourceFile()))
console.log('class', symbol.getName()) // console.log('class', symbol.getName())
const type = checker.getDeclaredTypeOfSymbol(symbol) const type = checker.getDeclaredTypeOfSymbol(symbol)
if (type.isClassOrInterface() && type.typeParameters) { if (type.isClassOrInterface() && type.typeParameters) {
type.typeParameters.forEach(tp => { type.typeParameters.forEach(tp => {
console.log(' tp.symbol.name', tp.symbol.name) // console.log(' tp.symbol.name', tp.symbol.name)
const constraint = tp.getConstraint() const constraint = tp.getConstraint()
if (constraint) { if (constraint) {
// TODO call getAllTypeParameters here... expandedTypeParameters.push(...getAllTypeParameters(tp))
console.log(' tp.constraint',
checker.typeToString(constraint))
} }
const def = tp.getDefault() const def = tp.getDefault()
if (def) { if (def) {
// TODO call getAllTypeParameters here... expandedTypeParameters.push(...getAllTypeParameters(tp))
console.log(' tp.default', checker.typeToString(def))
} }
typeParameters.push(tp) typeParameters.push(tp)
}) })
} }
// const properties = checker.getPropertiesOfType(type)
const properties = type.getApparentProperties() const properties = type.getApparentProperties()
console.log(' %o', properties const filterClassTypeParameters =
.filter(p => { (t: ts.Type) => typeParameters.every(tp => tp !== t)
const flags = ts.getCombinedModifierFlags(p.valueDeclaration)
return !(flags & ts.ModifierFlags.NonPublicAccessibilityModifier)
})
.map(p => {
const vd = p.valueDeclaration
const questionToken =
ts.isPropertyDeclaration(vd) && !!vd.questionToken
const propType = checker const classProperties: IClassProperty[] = properties
.getTypeOfSymbolAtLocation(p, p.valueDeclaration) .filter(filterInvisibleProperties)
.map(p => {
const vd = p.valueDeclaration
const optional = ts.isPropertyDeclaration(vd) && !!vd.questionToken
const typeParams = getAllTypeParameters(propType) const propType = checker.getTypeOfSymbolAtLocation(p, vd)
return {
name: p.getName(), const typeParams = getAllTypeParameters(propType)
type: checker.typeToString(propType),
questionToken, const relevantTypes = typeParams
typeParams: typeParams.map(typeToString), .filter(filterGlobalTypes)
filteredTypeParams: typeParams .filter(filterClassTypeParameters)
.filter(filterGlobalTypes) .map(mapGenericTypes)
// filter class type parameters .filter(filterDuplicates)
.filter(t => typeParameters.every(tp => tp !== t))
.map(mapGenericTypes) allRelevantTypes.push(...relevantTypes)
.filter(filterDuplicates)
.map(typeToString), return {
} name: p.getName(),
})) type: propType,
relevantTypes,
typeString: typeToString(type),
optional,
}
})
const relevantTypeParameters = expandedTypeParameters
.filter(filterGlobalTypes)
.filter(mapGenericTypes)
.filter(filterDuplicates)
allRelevantTypes.push(...relevantTypeParameters)
const classDef: IClassDefinition = {
name: symbol.getName(),
typeParameters,
allRelevantTypes: allRelevantTypes
.filter(filterClassTypeParameters)
.filter(filterDuplicates),
relevantTypeParameters,
properties: classProperties,
}
console.log(classDef.name)
console.log(' ',
classDef.properties
.map(p => p.name + ': ' + typeToString(p.type) + ' {' +
p.relevantTypes.map(typeToString) + '}')
.join('\n '),
)
console.log('\n allRelevantTypes:\n ',
classDef.allRelevantTypes.map(typeToString).join('\n '))
console.log('\n')
classDefs.push(classDef)
} }
} }
} }
/** True if this is visible outside this file, false otherwise */ // Visit every sourceFile in the program
function isNodeExported(node: ts.Node): boolean { for (const sourceFile of program.getSourceFiles()) {
return ( if (!sourceFile.isDeclarationFile) {
(ts.getCombinedModifierFlags(node as any) & // Walk the tree to search for classes
ts.ModifierFlags.Export) !== 0 || ts.forEachChild(sourceFile, visit)
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile) }
)
} }
} }