Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release @huolala-tech/[email protected] #23

Merged
merged 5 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ public interface NadClass extends NadDef {

@NonNull
List<NadMethod> getImportantMethods();

int getModifiers();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class NadClassImpl extends NadDefImpl implements NadClass {
@NonNull
private final List<NadMethod> importantMethods;

private final int modifiers;

/**
* Create a NadClass from a standard java class.
*
Expand All @@ -39,6 +41,8 @@ public class NadClassImpl extends NadDefImpl implements NadClass {
public NadClassImpl(Class<?> clz) {
super(clz);

modifiers = clz.getModifiers();

// For each generic type parameter, collect them and convert to type name strings.
typeParameters = Arrays.stream(clz.getTypeParameters()).map(NadContext::cc).collect(Collectors.toList());

Expand Down Expand Up @@ -99,4 +103,9 @@ public List<String> getInnerClasses() {
public List<NadMethod> getImportantMethods() {
return importantMethods;
}

@Override
public int getModifiers() {
return modifiers;
}
}
2 changes: 1 addition & 1 deletion packages/builder/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@huolala-tech/nad-builder",
"version": "1.1.2",
"version": "1.1.3",
"description": "Convert the Java AST to client-side code",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/codegen/CodeGenForOc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class CodeGenForOc extends CodeGen {
}

private writeDefs() {
const list = this.root.declarationList.slice(0).sort((a, b) => {
const list = this.root.classList.sort((a, b) => {
if (checkSuper(a, b)) return 1;
if (checkSuper(b, a)) return -1;
return 0;
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/codegen/CodeGenForTs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class CodeGenForTs extends CodeGen {
}

private writeClasses() {
for (const c of this.root.declarationList) {
for (const c of this.root.classList) {
this.writeComment(() => {
this.write(c.description || c.simpleName);
this.write(`@iface ${c.name}`);
Expand Down
81 changes: 67 additions & 14 deletions packages/builder/src/helpers/tsHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { neverReachHere } from '../utils/neverReachHere';
import { Class } from '../models/Class';
import type { Type } from '../models/Type';
import { Type } from '../models/Type';
import {
isJavaBoolean,
isJavaList,
Expand All @@ -13,7 +13,7 @@ import {
isJavaVoid,
isJavaWrapper,
} from './javaHelper';
import { toLowerCamel } from '../utils';
import { toLowerCamel, notEmpty } from '../utils';
import { RootOptions } from '../models/RootOptions';
import { Enum, TypeUsage } from '../models';

Expand All @@ -34,6 +34,16 @@ export const ss = (u: string | number | boolean) => {
throw neverReachHere(u);
};

const clzToTs = (clz: Class, parameters: readonly Type[]) => {
const { typeParameters, simpleName } = clz;
let t = simpleName;
if (typeParameters.length > 0) {
const pars = typeParameters.map((_, i) => t2s(parameters[i])).join(', ');
t += `<${pars}>`;
}
return t;
};

const TS_UNKNOWN = 'unknown';

const uTypeCache: Record<string, true | undefined> = Object.create(null);
Expand Down Expand Up @@ -104,22 +114,65 @@ export const t2s = (type: Type | undefined): string => {
if (!clz) return TS_UNKNOWN;

if (clz instanceof Class) {
const { typeParameters, simpleName } = clz;
let t = simpleName;
if (typeParameters.length > 0) {
const pars = typeParameters.map((_, i) => t2s(parameters[i])).join(', ');
t += `<${pars}>`;
}
if (type.usage === TypeUsage.superType) return clzToTs(clz, parameters);

if (type.usage === TypeUsage.superType) return t;
// Merge all derivatived classes as a TypeScript union type.
//
// For example:
//
// ```java
// abstract class Animal {}
// class Cat extends Animal {}
// class Dog extends Animal {}
// ```
//
// The `Animal` type will be `Cat | Dog` in TypeScript.
// NOTE: Normally, abstract class or interface will not be contained.
//
// Another example:
//
// ```java
// class BaseUserInfo {}
// class UserInfo extends BaseUserInfo {}
// ```
//
// The `BaseUserInfo` type will be `BaseUserInfo | UserInfo`, because BaseUseInfo is not an abstact class.
//
const union = [
// Place the current class and generic parameters.
[clz, parameters] as const,
// And add more derivatived refs with empty generic parameters.
...[...builder.findDerivativedRefs(name)]
.map(([c]) => {
if (!builder.isUsing(c)) return null;
return [c, []] as const;
})
.filter(notEmpty),
]
// Filter out some pairs which have no constructor.
.filter(([n]) => n.hasConstructor());

const { derivativedTypes } = clz;
if (derivativedTypes.length === 0) return t;
if (derivativedTypes.length === 1 && clz.isInterface) return t;
const ts = derivativedTypes.map(t2s);
if (!clz.isInterface) ts.push(t);
// TODO: Considers the generic parameter matching.
//
// For example:
//
// class Animal<T> {}
// class Cat extends Animal<Long> {}
// class Dog<G> extends Animal<G> {}
//
// If a method returns Animal<Long>, it will be extended to Cat and Dog<Long>.
// If a method returns Animal<Integer>, it will be extended to Dog<Integer> and cannot match Cat.
// If a method returns Animal<Long>, it will be extended to Cat and Dog<unknown>.
//
// But this feature is too complex, just mark this as a TODO temporarily.
//

if (union.length === 0) return clzToTs(clz, parameters);

const ts = union.map(([c, p]) => clzToTs(c, p));
return buildUnionType(...ts);
}

if (clz instanceof Enum) {
return clz.simpleName;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/builder/src/models/Annotated.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { hideProperty } from '../utils';
import { Annotations } from './annotations';
import { u2a, u2o, u2s } from 'u2x';

Expand All @@ -9,6 +10,7 @@ export class Annotated<T extends AnnocatedRaw = AnnocatedRaw & { [p in string]:
protected readonly raw;
constructor(raw?: T) {
this.raw = u2o(raw) as T;
hideProperty(this, 'raw');
this.name = u2s(this.raw.name) ?? '';
this.annotations = Annotations.create(u2a(this.raw.annotations));
}
Expand Down
59 changes: 31 additions & 28 deletions packages/builder/src/models/Class.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Member } from './Member';
import { Type, TypeUsage } from './Type';
import { Dubious, notEmpty } from '../utils';
import { u2a, u2s } from 'u2x';
import type { Root } from './Root';
import { Dubious, Modifier, notEmpty } from '../utils';
import { u2a, u2n, u2s } from 'u2x';
import { DefBase } from './DefBase';
import { NadClass } from '../types/nad';
import { Root } from './Root';

type ClassRaw = Dubious<NadClass>;

Expand All @@ -14,52 +14,55 @@ export class Class extends DefBase<ClassRaw> {
* For example, the value of typeParameters is [ "T", "M" ] for `class Foo<T, M> {}`
*/
public readonly typeParameters;
public readonly defName;
public readonly description;
public readonly modifiers;
public readonly bounds;

constructor(raw: ClassRaw, builder: Root) {
super(raw, builder);
constructor(raw: ClassRaw, parent: Root) {
super(raw, parent);
this.typeParameters = u2a(this.raw.typeParameters, u2s);

this.defName = this.simpleName;
this.modifiers = u2n(this.raw.modifiers);
this.bounds = [this.raw.superclass, ...u2a(this.raw.interfaces)].map((i) => u2s(i)).filter(notEmpty);

this.description = this.annotations.swagger.getApiModel()?.description;
}

public get defName() {
let value = this.simpleName;
if (this.typeParameters.length) {
const pars = this.typeParameters.join(', ');
this.defName += `<${pars}>`;
value += `<${pars}>`;
}
Object.defineProperty(this, 'defName', { configurable: true, value });
return value;
}

get members() {
public get members() {
// TODO: Declare interfaces and remove members which are duplicate with super interfaces.
const value = u2a(this.raw.members, (i) => new Member(i, this)).filter((m) => m.visible);
Object.defineProperty(this, 'members', { configurable: true, value });
return value;
}

get isInterface() {
return !this.raw.superclass;
}

get superclass() {
public get superclass() {
const value = Type.create(u2s(this.raw.superclass), this, TypeUsage.superType);
Object.defineProperty(this, 'superclass', { configurable: true, value });
return value;
}

get derivativedTypes() {
const { builder } = this;
const innserClassNameSet = new Set(u2a(this.raw.innerClasses, u2s).filter(notEmpty));
const value = builder
.findDerivativedTypes(this.name)
.filter((n) => !this.isInterface || innserClassNameSet.has(n))
.map((n) => Type.create(n, this));
Object.defineProperty(this, 'derivativedTypes', { configurable: true, value });
return value;
public hasConstructor() {
const { modifiers } = this;
if (modifiers === undefined) return true;
return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers);
}

spread() {
this.superclass.valueOf();
this.members.valueOf();
this.derivativedTypes.valueOf();
public spread() {
const { superclass, members, name, root: builder } = this;
superclass.valueOf();
members.valueOf();
// Touch all classes which is extending this class.
for (const [rawClz] of builder.findDerivativedRefs(name)) {
if (rawClz.hasConstructor()) builder.touchDef(rawClz);
}
}
}
19 changes: 13 additions & 6 deletions packages/builder/src/models/DefBase.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import { hideProperty } from '../utils';
import { AnnocatedRaw, Annotated } from './Annotated';
import type { Root } from './Root';

export class DefBase<T extends AnnocatedRaw = AnnocatedRaw> extends Annotated<T> {
public readonly simpleName;
public readonly builder;
public readonly root;
public get options() {
return this.builder.options;
return this.root.options;
}
constructor(raw: T, builder: Root) {
constructor(raw: T, root: Root) {
super(raw);
this.builder = builder;
this.simpleName = builder.takeUniqueName(this.name, this.options.fixClassName);
this.root = root;
hideProperty(this, 'root');
hideProperty(this, 'root');
}

public get simpleName() {
const value = this.root.takeUniqueName(this.name, this.options.fixClassName);
Object.defineProperty(this, 'simpleName', { configurable: true, value });
return value;
}
}
10 changes: 7 additions & 3 deletions packages/builder/src/models/Enum.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Dubious } from '../utils';
import { u2a } from 'u2x';
import type { Root } from './Root';
import { DefBase } from './DefBase';
import { EnumConstant } from './EnumConstant';
import { NadEnum } from '../types/nad';
import { Root } from './Root';

type EnumRaw = Dubious<NadEnum>;

export class Enum extends DefBase<EnumRaw> {
public readonly constants;
public readonly valueType;
public readonly description;
constructor(raw: EnumRaw, builder: Root) {
super(raw, builder);
constructor(raw: EnumRaw, root: Root) {
super(raw, root);
this.constants = u2a(this.raw.constants, (i) => new EnumConstant(i, this));
this.valueType = this.initValueType();
this.description = this.annotations.swagger.getApiModel()?.description;
Expand All @@ -25,4 +25,8 @@ export class Enum extends DefBase<EnumRaw> {
if (vt !== 'string' && vt !== 'number') vt = 'unknown' as const;
return vt;
}

public spread() {
// noop
}
}
Loading
Loading