intergen: Do not add I-prefix to interfaces

Renaming of existing types turned out to be too complicated because the
code depends on checker.typeToString, and there is no way that I know of
that would allow renaming of an existing type.
This commit is contained in:
Jerko Steiner 2019-08-14 15:43:56 +07:00
parent 25593dd994
commit b122ff093a
4 changed files with 186 additions and 12 deletions

View File

@ -1,4 +1,3 @@
export * from './build'
export * from './newlib'
export * from './intergen'
export * from './typecheck'

View File

@ -0,0 +1,160 @@
jest.mock('../log')
import {intergen} from './intergen'
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
describe('intergen', () => {
const tmpdir = os.tmpdir()
const templateName = 'intergen.tmp'
let sourceFiles: string[] = []
let i = 0
function createSourceFile(contents: string) {
i++
const sourceFile = path.join(tmpdir, templateName + i + '.ts')
fs.writeFileSync(sourceFile, contents)
sourceFiles.push(sourceFile)
return sourceFile
}
function execute(source: string): string {
const file = createSourceFile(source)
return intergen('intergen', '-i', file)
}
afterEach(() => {
sourceFiles.forEach(f => fs.unlinkSync(f))
sourceFiles = []
})
it('converts exported class into interface', () => {
const result = execute(`export class Value {
amount: number
}`)
expect(result).toEqual(`export interface Value {
amount: number
}`)
})
it('does nothing when class is not exported', () => {
const result = execute(`class Value {
amount: number
}`)
expect(result).toEqual(``)
})
it('converts inherited classes into interfaces', () => {
const result = execute(`class A {
a: number
}
export class B extends A {
b: string
}`)
expect(result).toEqual(`export interface B {
b: string
a: number
}`)
})
it('converts referenced classes into interfaces', () => {
const result = execute(`class A {
a: number
}
export class B {
a: A
}`)
expect(result).toEqual(`export interface B {
a: A
}
export interface A {
a: number
}`)
})
it('correctly converts union properties', () => {
const result = execute(`export class A {
a: 'b' | 'c'
}`)
expect(result).toEqual(`export interface A {
a: "b" | "c"
}`)
})
it('correctly converts array properties', () => {
const result = execute(`class Name {
firstName: string
}
export class Names {
names1: Name[]
names2: Array<Name>
names3: Name[][]
names4: Array<Array<Name>>
`)
expect(result).toEqual(`export interface Names {
names1: Name[]
names2: Name[]
names3: Name[][]
names4: Name[][]
}
export interface Name {
firstName: string
}`)
})
it('correctly converts intersections', () => {
const result = execute(`export class C {
c: A & B
}
class A {
a: string
}
class B {
b: number
}`)
expect(result).toEqual(`export interface C {
c: A & B
}
export interface A {
a: string
}
export interface B {
b: number
}`)
})
it('correctly converts inline property definitions', () => {
const result = execute(`export class A {
b: {a: string, c: number}
}`)
expect(result).toEqual(`export interface A {
b: { a: string; c: number; }
}`)
})
it('correctly converts inline defs w/ references', () => {
const result = execute(`class A {
a: number
}
export class B {
b: { a: A; c: number; }
}`)
expect(result).toEqual(`export interface B {
b: { a: A; c: number; }
}
export interface A {
a: number
}`)
})
})

View File

@ -11,6 +11,11 @@ function isTypeReference(type: ts.ObjectType): type is ts.TypeReference {
return !!(type.objectFlags & ts.ObjectFlags.Reference)
}
function isAnonymous(type: ts.Type): boolean {
return isObjectType(type) && !!(
type.objectFlags & ts.ObjectFlags.Anonymous)
}
function filterInvisibleProperties(type: ts.Symbol): boolean {
const flags = ts.getCombinedModifierFlags(type.valueDeclaration)
return !(flags & ts.ModifierFlags.NonPublicAccessibilityModifier)
@ -51,7 +56,7 @@ interface IClassDefinition {
*
*/
export function typecheck(...argv: string[]) {
export function intergen(...argv: string[]): string {
const args = argparse({
input: arg('string', {alias: 'i', required: true}),
debug: arg('boolean'),
@ -191,8 +196,8 @@ export function typecheck(...argv: string[]) {
function isNodeExported(node: ts.Node): boolean {
return (
(ts.getCombinedModifierFlags(node as any) &
ts.ModifierFlags.Export) !== 0 ||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
ts.ModifierFlags.Export) !== 0
// (!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
)
}
@ -296,7 +301,10 @@ export function typecheck(...argv: string[]) {
properties: classProperties,
}
classDefs.push(classDef)
if (!isAnonymous(type)) {
// Prevent defining anonymous declarations as interfaces
classDefs.push(classDef)
}
typeDefinitions.set(type, classDef)
classDef.allRelevantTypes.forEach(handleType)
@ -306,6 +314,11 @@ export function typecheck(...argv: string[]) {
* Visit nodes finding exported classes
*/
function visit(node: ts.Node) {
console.log(node.getText(),
isNodeExported(node),
ts.getCombinedModifierFlags(node as any),
!!node.parent,
node.parent.kind === ts.SyntaxKind.SourceFile)
// Only consider exported nodes
if (!isNodeExported(node)) {
return
@ -325,8 +338,12 @@ export function typecheck(...argv: string[]) {
}
function setTypeName(type: ts.Type, mappings: Map<ts.Type, string>) {
if (isAnonymous(type)) {
return
}
const name = typeToString(type)
mappings.set(type, `I${name}`)
// (type as any).symbol.name = 'I' + type.symbol.name
mappings.set(type, `${name}`)
}
const nameMappings = new Map<ts.Type, string>()
@ -339,7 +356,7 @@ export function typecheck(...argv: string[]) {
function createInterface(classDef: IClassDefinition): string {
const name = nameMappings.get(classDef.type)!
const start = `interface ${name} {`
const start = `export interface ${name} {`
const properties = classDef.properties.map(p => {
return ` ${p.name}: ${nameMappings.get(p.type) || p.typeString}`
})
@ -362,4 +379,5 @@ export function typecheck(...argv: string[]) {
} else {
fs.writeFileSync(args.output, value)
}
return value
}

View File

@ -1,12 +1,9 @@
import {format} from 'util'
const stdout: NodeJS.WriteStream = process.stdout
const stderr: NodeJS.WriteStream = process.stderr
export function error(message: string, ...values: any[]) {
stderr.write(format(message + '\n', ...values))
process.stderr.write(format(message + '\n', ...values))
}
export function info(message: string, ...values: any[]) {
stdout.write(format(message + '\n', ...values))
process.stdout.write(format(message + '\n', ...values))
}