From 2ded5ede0873955534091b963d4c9268193f684e Mon Sep 17 00:00:00 2001 From: YanagiEiichi <576398868@qq.com> Date: Mon, 30 Sep 2024 16:31:26 +0800 Subject: [PATCH 1/5] Add modifieds for NadClass --- .../java/cn/lalaframework/nad/interfaces/NadClass.java | 2 ++ .../java/cn/lalaframework/nad/models/NadClassImpl.java | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/nad-java-core/src/main/java/cn/lalaframework/nad/interfaces/NadClass.java b/nad-java-core/src/main/java/cn/lalaframework/nad/interfaces/NadClass.java index ce37cfe..8e919c6 100644 --- a/nad-java-core/src/main/java/cn/lalaframework/nad/interfaces/NadClass.java +++ b/nad-java-core/src/main/java/cn/lalaframework/nad/interfaces/NadClass.java @@ -23,4 +23,6 @@ public interface NadClass extends NadDef { @NonNull List getImportantMethods(); + + int getModifiers(); } diff --git a/nad-java-core/src/main/java/cn/lalaframework/nad/models/NadClassImpl.java b/nad-java-core/src/main/java/cn/lalaframework/nad/models/NadClassImpl.java index bd024f7..0f5c0ce 100644 --- a/nad-java-core/src/main/java/cn/lalaframework/nad/models/NadClassImpl.java +++ b/nad-java-core/src/main/java/cn/lalaframework/nad/models/NadClassImpl.java @@ -31,6 +31,8 @@ public class NadClassImpl extends NadDefImpl implements NadClass { @NonNull private final List importantMethods; + private final int modifiers; + /** * Create a NadClass from a standard java class. * @@ -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()); @@ -99,4 +103,9 @@ public List getInnerClasses() { public List getImportantMethods() { return importantMethods; } + + @Override + public int getModifiers() { + return modifiers; + } } \ No newline at end of file From c4e15370a604cf1ec98ba3cbacafacc757cb8082 Mon Sep 17 00:00:00 2001 From: YanagiEiichi <576398868@qq.com> Date: Wed, 9 Oct 2024 10:08:58 +0800 Subject: [PATCH 2/5] Refactor the implementation of union types --- packages/builder/src/helpers/tsHelper.ts | 60 ++- packages/builder/src/models/Class.ts | 32 +- packages/builder/src/models/Root.ts | 90 ++-- packages/builder/src/models/Type.ts | 4 +- .../src/tests/codegen/ts-types.test.ts | 12 +- .../builder/src/tests/models/Root.test.ts | 8 +- packages/builder/src/types/nad.ts | 3 + packages/builder/src/utils/Modifiers.ts | 395 ++++++++++++++++++ packages/builder/src/utils/index.ts | 1 + 9 files changed, 535 insertions(+), 70 deletions(-) create mode 100644 packages/builder/src/utils/Modifiers.ts diff --git a/packages/builder/src/helpers/tsHelper.ts b/packages/builder/src/helpers/tsHelper.ts index 3af420f..654da2b 100644 --- a/packages/builder/src/helpers/tsHelper.ts +++ b/packages/builder/src/helpers/tsHelper.ts @@ -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, @@ -13,7 +13,7 @@ import { isJavaVoid, isJavaWrapper, } from './javaHelper'; -import { toLowerCamel } from '../utils'; +import { toLowerCamel, Modifier, notEmpty } from '../utils'; import { RootOptions } from '../models/RootOptions'; import { Enum, TypeUsage } from '../models'; @@ -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 = Object.create(null); @@ -104,22 +114,44 @@ 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; + const refs = [ + // Place the current class and generic parameters. + [clz, parameters] as const, + // And add more derivatived refs with empty generic parameters. + ...[...builder.findDerivativedRefs(name)] + .map(([n]) => { + const c = builder.getClass(n); + if (!c) return null; + return [c, []] as const; + }) + .filter(notEmpty), + ] + // Filter out some pairs which have no constructor. + .filter(([n]) => builder.hasConstructor(n.name)); - 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: + // + // interface Animal {} + // class Cat extends Animal {} + // class Dog extends Animal {} + // + // If a method returns Animal, it will be extended to Cat and Dog. + // If a method returns Animal, it will be extended to Dog and cannot match Cat. + // If a method returns Animal, it will be extended to Cat and Dog. + // + // But this feature is too complex, just mark this as a TODO temporarily. + // + + if (refs.length === 0) return clzToTs(clz, parameters); + + const ts = refs.map(([c, p]) => clzToTs(c, p)); return buildUnionType(...ts); } + if (clz instanceof Enum) { return clz.simpleName; } diff --git a/packages/builder/src/models/Class.ts b/packages/builder/src/models/Class.ts index 312d670..65f73c2 100644 --- a/packages/builder/src/models/Class.ts +++ b/packages/builder/src/models/Class.ts @@ -1,10 +1,11 @@ import { Member } from './Member'; import { Type, TypeUsage } from './Type'; import { Dubious, notEmpty } from '../utils'; -import { u2a, u2s } from 'u2x'; +import { u2a, u2n, u2s } from 'u2x'; import type { Root } from './Root'; import { DefBase } from './DefBase'; import { NadClass } from '../types/nad'; +import { Modifier } from '../utils'; type ClassRaw = Dubious; @@ -16,11 +17,12 @@ export class Class extends DefBase { public readonly typeParameters; public readonly defName; public readonly description; + public readonly modifiers; constructor(raw: ClassRaw, builder: Root) { super(raw, builder); this.typeParameters = u2a(this.raw.typeParameters, u2s); - + this.modifiers = u2n(this.raw.modifiers) ?? 0; this.defName = this.simpleName; this.description = this.annotations.swagger.getApiModel()?.description; @@ -31,35 +33,25 @@ export class Class extends DefBase { } 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() { 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; - } - spread() { - this.superclass.valueOf(); - this.members.valueOf(); - this.derivativedTypes.valueOf(); + const { superclass, members, name, builder } = this; + superclass.valueOf(); + members.valueOf(); + // Touch all classes which is extending this class. + for (const [n] of builder.findDerivativedRefs(name)) { + if (builder.hasConstructor(n)) Type.create(n, this); + } } } diff --git a/packages/builder/src/models/Root.ts b/packages/builder/src/models/Root.ts index 303974f..7d00076 100644 --- a/packages/builder/src/models/Root.ts +++ b/packages/builder/src/models/Root.ts @@ -1,7 +1,16 @@ import { Module } from './Module'; import { Class } from './Class'; -import { computeIfAbsent, Dubious, toUpperCamel, parseDsv, UniqueName, removeDynamicSuffix, notEmpty } from '../utils'; -import { u2o, u2a, u2s } from 'u2x'; +import { + computeIfAbsent, + Dubious, + toUpperCamel, + parseDsv, + UniqueName, + removeDynamicSuffix, + notEmpty, + Modifier, +} from '../utils'; +import { u2o, u2a, u2s, u2n } from 'u2x'; import { Enum } from './Enum'; import { NadResult } from '../types/nad'; import { RouteRaw } from './Route'; @@ -32,19 +41,7 @@ export class Root { const rawClasses = u2a(raw.classes, u2o); this.rawClassMap = new Map(rawClasses.map((i) => [u2s(u2o(i).name), i])); - - this.derivationMap = rawClasses.reduce((map, clz) => { - const { superclass, interfaces, name } = clz; - if (typeof name !== 'string') return map; - [superclass] - .concat(interfaces) - .map((s) => u2s(s)) - .filter(notEmpty) - .map((n) => n.replace(/\<.*/, '')) - .filter((n) => this.rawClassMap.has(n)) - .forEach((n) => computeIfAbsent(map, n, () => []).push(name)); - return map; - }, new Map()); + this.derivationMap = this.buildDerivationMap(); this.rawEnumMap = new Map(u2a(raw.enums, (i) => [u2s(u2o(i).name), i])); this.rawModuleMap = new Map(u2a(raw.modules, (i) => [u2s(u2o(i).name), i])); @@ -77,6 +74,35 @@ export class Root { ); } + private buildDerivationMap() { + const { rawClassMap } = this; + + // For example: + // If the classes defining like follow Java code + // + // class Animal {} + // class Dog extends Animal {} + // class Cat extends Animal {} + // + // The map result will be: + // + // { 'Animal': { 'Dog': 'Animal', 'Cat': 'Animal' } } + // + const map = new Map>(); + for (const [, clz] of rawClassMap) { + const { superclass, interfaces, name } = clz; + if (typeof name !== 'string') continue; + for (const n of [superclass, ...u2a(interfaces)]) { + if (typeof n !== 'string' || !n) continue; + const sn = n.replace(/\<.*/, ''); + if (!rawClassMap.has(sn)) continue; + computeIfAbsent(map, n, () => new Map()).set(name, n); + } + } + + return map; + } + get declarationList() { return Object.values(this.classes); } @@ -95,18 +121,27 @@ export class Root { return this.modules.reduce((s, i) => s + i.routes.length, 0); } - public getDefByName(name: string): Enum | Class | null { - const { classes, rawClassMap: rawClasses, enums, rawEnumMap: rawEnums } = this; + public hasConstructor(name: string) { + const { rawClassMap } = this; + let raw = rawClassMap.get(name); + if (!raw) return false; + const modifiers = u2n(raw.modifiers); + if (modifiers === undefined) return true; + return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); + } + + public touchDef(name: string): Enum | Class | null { + const { classes, rawClassMap, enums, rawEnumMap } = this; if (name in classes) return classes[name]; if (name in enums) return enums[name]; - let raw = rawClasses.get(name); + let raw = rawClassMap.get(name); if (raw) { const clz = new Class(raw, this); classes[name] = clz; clz.spread(); return clz; } - raw = rawEnums.get(name); + raw = rawEnumMap.get(name); if (raw) { const en = new Enum(raw, this); enums[name] = en; @@ -116,6 +151,12 @@ export class Root { return null; } + public getClass(name: string) { + const { classes } = this; + if (name in classes) return classes[name]; + return null; + } + public takeUniqueName(javaClassPath: string, fixFunction: (s: string) => string) { // Concat the java package path before name, while the class name is already in use. const { uniqueNameSeparator } = this; @@ -128,10 +169,11 @@ export class Root { return UniqueName.createFor(this, fixFunction(name), uniqueNameSeparator); } - public findDerivativedTypes(rawTypeName: string) { - // TODO: Support generic type matching. - const list = this.derivationMap.get(rawTypeName); - if (!list) return []; - return list; + public *findDerivativedRefs(name: string, depth = 0): Iterable<[string, string]> { + if (depth > 20) throw new Error('Too many nested derivative class: ' + name); + for (const [who, usage] of this.derivationMap?.get(name) ?? []) { + yield [who, usage]; + yield* this.findDerivativedRefs(who, depth + 1); + } } } diff --git a/packages/builder/src/models/Type.ts b/packages/builder/src/models/Type.ts index b9ecb28..795bca7 100644 --- a/packages/builder/src/models/Type.ts +++ b/packages/builder/src/models/Type.ts @@ -44,7 +44,7 @@ export class Type { if (isJavaNonClass(name) || this.isGenericVariable) { this.clz = null; } else { - this.clz = this.builder.getDefByName(name); + this.clz = this.builder.touchDef(name); } } @@ -113,7 +113,7 @@ export class Type { parameters = []; name = JAVA_STRING; } else { - parameters = [new Type(owner, usage, name, parameters)]; + parameters = [new this(owner, usage, name, parameters)]; name = JAVA_LIST; } } diff --git a/packages/builder/src/tests/codegen/ts-types.test.ts b/packages/builder/src/tests/codegen/ts-types.test.ts index 47dd84e..0f03a69 100644 --- a/packages/builder/src/tests/codegen/ts-types.test.ts +++ b/packages/builder/src/tests/codegen/ts-types.test.ts @@ -1,7 +1,7 @@ import { Builder } from '../../Builder'; import { NadClass, NadEnum, NadRoute } from '../../types/nad'; import { paginitionDefs } from '../defs/paginitionTestDefs'; -import { DeepPartial } from '../../utils'; +import { DeepPartial, Modifier } from '../../utils'; test('Paginition', () => { const code = new Builder({ target: 'ts', base: '', defs: paginitionDefs }).code; @@ -52,12 +52,12 @@ test('empty bean', () => { test('union type', () => { const classes: DeepPartial[] = [ - { name: 'test.U', members: [], innerClasses: ['test.U$A', 'test.U$B'] }, + { name: 'test.U', members: [], innerClasses: ['test.U$A', 'test.U$B'], modifiers: Modifier.INTERFACE }, { name: 'test.U$A', superclass: 'java.lang.Object', members: [], interfaces: ['test.U'] }, { name: 'test.U$B', superclass: 'java.lang.Object', members: [], interfaces: ['test.U'] }, - { name: 'test.M', members: [], innerClasses: ['test.M$A'] }, - { name: 'test.M$A', superclass: 'java.lang.Object', members: [], interfaces: ['test.M'] }, + { name: 'test.M', members: [], innerClasses: ['test.M$A'], modifiers: Modifier.INTERFACE }, + { name: 'test.M$A', members: [], interfaces: ['test.M'], modifiers: Modifier.INTERFACE }, { name: 'test.G', members: [], innerClasses: ['test.G$A'], superclass: 'java.lang.Object' }, { name: 'test.G$A', superclass: 'test.G', members: [] }, @@ -93,8 +93,8 @@ test('union type', () => { /** * foo3 */ - async foo3(a?: Array, settings?: Partial) { - return new Runtime() + async foo3(a?: Array, settings?: Partial) { + return new Runtime() .open('POST', '', settings) .addRequestParam('a', a) .execute(); diff --git a/packages/builder/src/tests/models/Root.test.ts b/packages/builder/src/tests/models/Root.test.ts index 4252da2..8502516 100644 --- a/packages/builder/src/tests/models/Root.test.ts +++ b/packages/builder/src/tests/models/Root.test.ts @@ -75,13 +75,13 @@ test('defs', () => { ); }; - const user = root.getDefByName('test.User'); - expect(root.getDefByName('test.User')).toBe(user); // from cache + const user = root.touchDef('test.User'); + expect(root.touchDef('test.User')).toBe(user); // from cache expect(user).toBeInstanceOf(Class); if (user) expect(getDefBySimpleName(root, user.simpleName)).toBe(user); - const userType = root.getDefByName('test.UserType'); - expect(root.getDefByName('test.UserType')).toBe(userType); // from cache + const userType = root.touchDef('test.UserType'); + expect(root.touchDef('test.UserType')).toBe(userType); // from cache expect(userType).toBeInstanceOf(Enum); if (userType) expect(getDefBySimpleName(root, userType.simpleName)).toBe(userType); diff --git a/packages/builder/src/types/nad.ts b/packages/builder/src/types/nad.ts index 4ac36a3..7a19470 100644 --- a/packages/builder/src/types/nad.ts +++ b/packages/builder/src/types/nad.ts @@ -20,6 +20,7 @@ export interface NadClass extends NadDef { typeParameters: string[]; innerClasses: string[]; importantMethods: NadMethod[]; + modifiers: number; } /** @@ -81,7 +82,9 @@ export type NadModule = NadDef; */ export interface NadMethod extends NadDef { parameters: NadParameter[]; + typeParameters: NadParameter[]; returnType: string; + modifiers: number; } /** diff --git a/packages/builder/src/utils/Modifiers.ts b/packages/builder/src/utils/Modifiers.ts new file mode 100644 index 0000000..b20de90 --- /dev/null +++ b/packages/builder/src/utils/Modifiers.ts @@ -0,0 +1,395 @@ +/* istanbul ignore file */ +/** + * From java.lang.reflect.Modifier + */ +export abstract class Modifier { + /** + * Return {@code true} if the integer argument includes the + * {@code public} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code public} modifier; {@code false} otherwise. + */ + public static isPublic(mod: number): boolean { + return (mod & Modifier.PUBLIC) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code private} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code private} modifier; {@code false} otherwise. + */ + public static isPrivate(mod: number): boolean { + return (mod & Modifier.PRIVATE) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code protected} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code protected} modifier; {@code false} otherwise. + */ + public static isProtected(mod: number): boolean { + return (mod & Modifier.PROTECTED) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code static} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code static} modifier; {@code false} otherwise. + */ + public static isStatic(mod: number): boolean { + return (mod & Modifier.STATIC) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code final} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code final} modifier; {@code false} otherwise. + */ + public static isFinal(mod: number): boolean { + return (mod & Modifier.FINAL) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code synchronized} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code synchronized} modifier; {@code false} otherwise. + */ + public static isSynchronized(mod: number): boolean { + return (mod & Modifier.SYNCHRONIZED) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code volatile} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code volatile} modifier; {@code false} otherwise. + */ + public static isVolatile(mod: number): boolean { + return (mod & Modifier.VOLATILE) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code transient} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code transient} modifier; {@code false} otherwise. + */ + public static isTransient(mod: number): boolean { + return (mod & Modifier.TRANSIENT) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code native} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code native} modifier; {@code false} otherwise. + */ + public static isNative(mod: number): boolean { + return (mod & Modifier.NATIVE) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code interface} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code interface} modifier; {@code false} otherwise. + */ + public static isInterface(mod: number): boolean { + return (mod & Modifier.INTERFACE) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code abstract} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code abstract} modifier; {@code false} otherwise. + */ + public static isAbstract(mod: number): boolean { + return (mod & Modifier.ABSTRACT) !== 0; + } + + /** + * Return {@code true} if the integer argument includes the + * {@code strictfp} modifier, {@code false} otherwise. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code strictfp} modifier; {@code false} otherwise. + */ + public static isStrict(mod: number): boolean { + return (mod & Modifier.STRICT) !== 0; + } + + /* + * Access modifier flag constants from tables 4.1, 4.4, 4.5, and 4.7 of + * The Java Virtual Machine Specification + */ + + /** + * The {@code int} value representing the {@code public} + * modifier. + */ + public static PUBLIC = 0x00000001; + + /** + * The {@code int} value representing the {@code private} + * modifier. + */ + public static PRIVATE = 0x00000002; + + /** + * The {@code int} value representing the {@code protected} + * modifier. + */ + public static PROTECTED = 0x00000004; + + /** + * The {@code int} value representing the {@code static} + * modifier. + */ + public static STATIC = 0x00000008; + + /** + * The {@code int} value representing the {@code final} + * modifier. + */ + public static FINAL = 0x00000010; + + /** + * The {@code int} value representing the {@code synchronized} + * modifier. + */ + public static SYNCHRONIZED = 0x00000020; + + /** + * The {@code int} value representing the {@code volatile} + * modifier. + */ + public static VOLATILE = 0x00000040; + + /** + * The {@code int} value representing the {@code transient} + * modifier. + */ + public static TRANSIENT = 0x00000080; + + /** + * The {@code int} value representing the {@code native} + * modifier. + */ + public static NATIVE = 0x00000100; + + /** + * The {@code int} value representing the {@code interface} + * modifier. + */ + public static INTERFACE = 0x00000200; + + /** + * The {@code int} value representing the {@code abstract} + * modifier. + */ + public static ABSTRACT = 0x00000400; + + /** + * The {@code int} value representing the {@code strictfp} + * modifier. + */ + public static STRICT = 0x00000800; + + // Bits not (yet) exposed in the public API either because they + // have different meanings for fields and methods and there is no + // way to distinguish between the two in this class, or because + // they are not Java programming language keywords + static BRIDGE = 0x00000040; + static VARARGS = 0x00000080; + static SYNTHETIC = 0x00001000; + static ANNOTATION = 0x00002000; + static ENUM = 0x00004000; + static MANDATED = 0x00008000; + + static isSynthetic(mod: number): boolean { + return (mod & Modifier.SYNTHETIC) !== 0; + } + + static isMandated(mod: number): boolean { + return (mod & Modifier.MANDATED) !== 0; + } + + // Note on the FOO_MODIFIERS fields and fooModifiers() methods: + // the sets of modifiers are not guaranteed to be constants + // across time and Java SE releases. Therefore, it would not be + // appropriate to expose an external interface to this information + // that would allow the values to be treated as Java-level + // constants since the values could be constant folded and updates + // to the sets of modifiers missed. Thus, the fooModifiers() + // methods return an unchanging values for a given release, but a + // value that can potentially change over time. + + /** + * The Java source modifiers that can be applied to a class. + * @jls 8.1.1 Class Modifiers + */ + private static CLASS_MODIFIERS = + Modifier.PUBLIC | + Modifier.PROTECTED | + Modifier.PRIVATE | + Modifier.ABSTRACT | + Modifier.STATIC | + Modifier.FINAL | + Modifier.STRICT; + + /** + * The Java source modifiers that can be applied to an interface. + * @jls 9.1.1 Interface Modifiers + */ + private static INTERFACE_MODIFIERS = + Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE | Modifier.ABSTRACT | Modifier.STATIC | Modifier.STRICT; + + /** + * The Java source modifiers that can be applied to a constructor. + * @jls 8.8.3 Constructor Modifiers + */ + private static CONSTRUCTOR_MODIFIERS = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; + + /** + * The Java source modifiers that can be applied to a method. + * @jls 8.4.3 Method Modifiers + */ + private static METHOD_MODIFIERS = + Modifier.PUBLIC | + Modifier.PROTECTED | + Modifier.PRIVATE | + Modifier.ABSTRACT | + Modifier.STATIC | + Modifier.FINAL | + Modifier.SYNCHRONIZED | + Modifier.NATIVE | + Modifier.STRICT; + + /** + * The Java source modifiers that can be applied to a field. + * @jls 8.3.1 Field Modifiers + */ + private static FIELD_MODIFIERS = + Modifier.PUBLIC | + Modifier.PROTECTED | + Modifier.PRIVATE | + Modifier.STATIC | + Modifier.FINAL | + Modifier.TRANSIENT | + Modifier.VOLATILE; + + /** + * The Java source modifiers that can be applied to a method or constructor parameter. + * @jls 8.4.1 Formal Parameters + */ + private static PARAMETER_MODIFIERS = Modifier.FINAL; + + static ACCESS_MODIFIERS = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a class. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a class. + * + * @jls 8.1.1 Class Modifiers + * @since 1.7 + */ + public static classModifiers() { + return Modifier.CLASS_MODIFIERS; + } + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to an interface. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to an interface. + * + * @jls 9.1.1 Interface Modifiers + * @since 1.7 + */ + public static interfaceModifiers() { + return Modifier.INTERFACE_MODIFIERS; + } + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a constructor. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a constructor. + * + * @jls 8.8.3 Constructor Modifiers + * @since 1.7 + */ + public static constructorModifiers() { + return Modifier.CONSTRUCTOR_MODIFIERS; + } + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a method. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a method. + * + * @jls 8.4.3 Method Modifiers + * @since 1.7 + */ + public static methodModifiers() { + return Modifier.METHOD_MODIFIERS; + } + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a field. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a field. + * + * @jls 8.3.1 Field Modifiers + * @since 1.7 + */ + public static fieldModifiers() { + return Modifier.FIELD_MODIFIERS; + } + + /** + * Return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a parameter. + * @return an {@code int} value OR-ing together the source language + * modifiers that can be applied to a parameter. + * + * @jls 8.4.1 Formal Parameters + * @since 1.8 + */ + public static parameterModifiers() { + return Modifier.PARAMETER_MODIFIERS; + } +} diff --git a/packages/builder/src/utils/index.ts b/packages/builder/src/utils/index.ts index 7885bf1..8845f74 100644 --- a/packages/builder/src/utils/index.ts +++ b/packages/builder/src/utils/index.ts @@ -3,6 +3,7 @@ export * from './UniqueName'; export * from './computeIfAbsent'; export * from './neverReachHere'; export * from './parseDsv'; +export * from './Modifiers'; export const notEmpty = (w: T): w is NonNullable => w !== null && w !== undefined; From 53ee4c2df28727cf01b380ae28430950456bd213 Mon Sep 17 00:00:00 2001 From: YanagiEiichi <576398868@qq.com> Date: Wed, 9 Oct 2024 17:54:49 +0800 Subject: [PATCH 3/5] Add RawDef model --- packages/builder/src/helpers/tsHelper.ts | 4 +- packages/builder/src/models/Class.ts | 25 +++--- packages/builder/src/models/Enum.ts | 6 +- packages/builder/src/models/RawDef.ts | 49 +++++++++++ packages/builder/src/models/Root.ts | 82 ++++++++++--------- packages/builder/src/models/index.ts | 1 + .../src/tests/codegen/all-types.test.ts | 4 +- .../src/tests/codegen/oc-types.test.ts | 33 ++++++++ .../builder/src/tests/models/Class.test.ts | 24 +++--- .../builder/src/tests/models/Enum.test.ts | 26 +++--- .../src/tests/models/annotations.test.ts | 6 +- packages/builder/src/utils/index.ts | 15 ++++ 12 files changed, 194 insertions(+), 81 deletions(-) create mode 100644 packages/builder/src/models/RawDef.ts diff --git a/packages/builder/src/helpers/tsHelper.ts b/packages/builder/src/helpers/tsHelper.ts index 654da2b..529a068 100644 --- a/packages/builder/src/helpers/tsHelper.ts +++ b/packages/builder/src/helpers/tsHelper.ts @@ -122,14 +122,14 @@ export const t2s = (type: Type | undefined): string => { // And add more derivatived refs with empty generic parameters. ...[...builder.findDerivativedRefs(name)] .map(([n]) => { - const c = builder.getClass(n); + const c = builder.getClass(n.name); if (!c) return null; return [c, []] as const; }) .filter(notEmpty), ] // Filter out some pairs which have no constructor. - .filter(([n]) => builder.hasConstructor(n.name)); + .filter(([n]) => n.hasConstructor()); // TODO: Considers the generic parameter matching. // diff --git a/packages/builder/src/models/Class.ts b/packages/builder/src/models/Class.ts index 65f73c2..9376ed6 100644 --- a/packages/builder/src/models/Class.ts +++ b/packages/builder/src/models/Class.ts @@ -1,11 +1,10 @@ import { Member } from './Member'; import { Type, TypeUsage } from './Type'; -import { Dubious, notEmpty } from '../utils'; -import { u2a, u2n, u2s } from 'u2x'; -import type { Root } from './Root'; +import { Dubious } from '../utils'; +import { u2a, u2s } from 'u2x'; import { DefBase } from './DefBase'; import { NadClass } from '../types/nad'; -import { Modifier } from '../utils'; +import { RawClass } from './RawDef'; type ClassRaw = Dubious; @@ -18,11 +17,13 @@ export class Class extends DefBase { public readonly defName; public readonly description; public readonly modifiers; + private readonly rawClass; - constructor(raw: ClassRaw, builder: Root) { - super(raw, builder); + constructor(rawClass: RawClass) { + super(rawClass.raw, rawClass.root); + this.rawClass = rawClass; this.typeParameters = u2a(this.raw.typeParameters, u2s); - this.modifiers = u2n(this.raw.modifiers) ?? 0; + this.modifiers = rawClass.modifiers; this.defName = this.simpleName; this.description = this.annotations.swagger.getApiModel()?.description; @@ -45,13 +46,17 @@ export class Class extends DefBase { return value; } - spread() { + public hasConstructor() { + return this.rawClass.hasConstructor(); + } + + public spread() { const { superclass, members, name, builder } = this; superclass.valueOf(); members.valueOf(); // Touch all classes which is extending this class. - for (const [n] of builder.findDerivativedRefs(name)) { - if (builder.hasConstructor(n)) Type.create(n, this); + for (const [rawClz] of builder.findDerivativedRefs(name)) { + if (rawClz.hasConstructor()) rawClz.use(); } } } diff --git a/packages/builder/src/models/Enum.ts b/packages/builder/src/models/Enum.ts index 6d70357..e96473d 100644 --- a/packages/builder/src/models/Enum.ts +++ b/packages/builder/src/models/Enum.ts @@ -1,9 +1,9 @@ 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 { RawEnum } from './RawDef'; type EnumRaw = Dubious; @@ -11,8 +11,8 @@ export class Enum extends DefBase { public readonly constants; public readonly valueType; public readonly description; - constructor(raw: EnumRaw, builder: Root) { - super(raw, builder); + constructor({ raw, root }: RawEnum) { + 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; diff --git a/packages/builder/src/models/RawDef.ts b/packages/builder/src/models/RawDef.ts new file mode 100644 index 0000000..7204f40 --- /dev/null +++ b/packages/builder/src/models/RawDef.ts @@ -0,0 +1,49 @@ +import { hideProperty, Modifier, notEmpty } from '../utils'; +import { u2o, u2s, u2a, u2n } from 'u2x'; +import { Root } from './Root'; + +abstract class RawDef { + public readonly raw; + public readonly root; + public readonly name; + constructor(raw: unknown, root: Root) { + this.root = root; + this.raw = u2o(raw); + this.name = u2s(this.raw.name) ?? ''; + hideProperty(this, 'raw'); + hideProperty(this, 'root'); + } +} + +export class RawClass extends RawDef { + public readonly superclass; + public readonly interfaces; + public readonly modifiers; + + constructor(raw: unknown, root: Root) { + super(raw, root); + this.superclass = u2s(this.raw.superclass) ?? null; + this.interfaces = u2a(this.raw.interfaces, (u) => u2s(u)).filter(notEmpty); + this.modifiers = u2n(this.raw.modifiers); + } + + public hasConstructor() { + const {modifiers} = this; + if (modifiers === undefined) return true; + return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); + } + + public use() { + return this.root.touchClass(this); + } +} + +export class RawEnum extends RawDef { + constructor(raw: unknown, root: Root) { + super(raw, root); + } + + public use() { + return this.root.touchEnum(this); + } +} diff --git a/packages/builder/src/models/Root.ts b/packages/builder/src/models/Root.ts index 7d00076..c96c206 100644 --- a/packages/builder/src/models/Root.ts +++ b/packages/builder/src/models/Root.ts @@ -7,8 +7,8 @@ import { parseDsv, UniqueName, removeDynamicSuffix, - notEmpty, Modifier, + assert, } from '../utils'; import { u2o, u2a, u2s, u2n } from 'u2x'; import { Enum } from './Enum'; @@ -16,6 +16,7 @@ import { NadResult } from '../types/nad'; import { RouteRaw } from './Route'; import { RootOptions } from './RootOptions'; import { CommonDefs } from './CommonDefs'; +import { RawClass, RawEnum } from './RawDef'; export type RawDefs = Dubious; @@ -39,11 +40,9 @@ export class Root { constructor(raw: RawDefs, options: Partial = {}) { this.options = new RootOptions(options); - const rawClasses = u2a(raw.classes, u2o); - this.rawClassMap = new Map(rawClasses.map((i) => [u2s(u2o(i).name), i])); + this.rawClassMap = new Map(u2a(raw.classes, (i) => new RawClass(i, this)).map((d) => [d.name, d] as const)); this.derivationMap = this.buildDerivationMap(); - - this.rawEnumMap = new Map(u2a(raw.enums, (i) => [u2s(u2o(i).name), i])); + this.rawEnumMap = new Map(u2a(raw.enums, (i) => new RawEnum(i, this)).map((d) => [d.name, d] as const)); this.rawModuleMap = new Map(u2a(raw.modules, (i) => [u2s(u2o(i).name), i])); this.classes = Object.create(null); @@ -88,15 +87,15 @@ export class Root { // // { 'Animal': { 'Dog': 'Animal', 'Cat': 'Animal' } } // - const map = new Map>(); + const map = new Map>(); for (const [, clz] of rawClassMap) { - const { superclass, interfaces, name } = clz; - if (typeof name !== 'string') continue; + const { superclass, interfaces } = clz; for (const n of [superclass, ...u2a(interfaces)]) { - if (typeof n !== 'string' || !n) continue; + if (!n) continue; const sn = n.replace(/\<.*/, ''); if (!rawClassMap.has(sn)) continue; - computeIfAbsent(map, n, () => new Map()).set(name, n); + const item = computeIfAbsent(map, n, () => new Map()); + item.set(clz, n); } } @@ -121,32 +120,41 @@ export class Root { return this.modules.reduce((s, i) => s + i.routes.length, 0); } - public hasConstructor(name: string) { - const { rawClassMap } = this; - let raw = rawClassMap.get(name); - if (!raw) return false; - const modifiers = u2n(raw.modifiers); - if (modifiers === undefined) return true; - return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); + public getRawDef(name: string) { + const { rawClassMap, rawEnumMap } = this; + return rawClassMap.get(name) || rawEnumMap.get(name) || null; + } + + /** + * This method can only be called from RawEnum. + */ + public touchEnum(raw: RawEnum) { + const { enums } = this; + if (raw.name in enums) return enums[raw.name]; + const en = new Enum(raw); + enums[raw.name] = en; + return en; + } + + /** + * This method can only be called from RawClass. + */ + public touchClass(raw: RawClass) { + const { classes } = this; + if (raw.name in classes) return classes[raw.name]; + const clz = new Class(raw); + classes[raw.name] = clz; + clz.spread(); + return clz; } + /** + * This method can only be called from Type. + */ public touchDef(name: string): Enum | Class | null { - const { classes, rawClassMap, enums, rawEnumMap } = this; - if (name in classes) return classes[name]; - if (name in enums) return enums[name]; - let raw = rawClassMap.get(name); - if (raw) { - const clz = new Class(raw, this); - classes[name] = clz; - clz.spread(); - return clz; - } - raw = rawEnumMap.get(name); - if (raw) { - const en = new Enum(raw, this); - enums[name] = en; - return en; - } + const raw = this.getRawDef(name); + if (raw instanceof RawClass) return this.touchClass(raw); + if (raw instanceof RawEnum) return this.touchEnum(raw); this.unknownTypes.add(name); return null; } @@ -169,11 +177,11 @@ export class Root { return UniqueName.createFor(this, fixFunction(name), uniqueNameSeparator); } - public *findDerivativedRefs(name: string, depth = 0): Iterable<[string, string]> { - if (depth > 20) throw new Error('Too many nested derivative class: ' + name); - for (const [who, usage] of this.derivationMap?.get(name) ?? []) { + public *findDerivativedRefs(name: string, depth = 0): Iterable<[RawClass, string]> { + assert(depth < 20, 'Too many nested derivative class: ' + name); + for (const [who, usage] of this.derivationMap.get(name) ?? []) { yield [who, usage]; - yield* this.findDerivativedRefs(who, depth + 1); + yield* this.findDerivativedRefs(who.name, depth + 1); } } } diff --git a/packages/builder/src/models/index.ts b/packages/builder/src/models/index.ts index f2e9311..45af3ea 100644 --- a/packages/builder/src/models/index.ts +++ b/packages/builder/src/models/index.ts @@ -10,3 +10,4 @@ export * from './Parameter'; export * from './Root'; export * from './Route'; export * from './Type'; +export * from './RawDef'; diff --git a/packages/builder/src/tests/codegen/all-types.test.ts b/packages/builder/src/tests/codegen/all-types.test.ts index 260b74d..6ed44b2 100644 --- a/packages/builder/src/tests/codegen/all-types.test.ts +++ b/packages/builder/src/tests/codegen/all-types.test.ts @@ -1,6 +1,6 @@ import { buildOcMethodWithParameters, buildTsMethodWithParameters } from '../test-tools/buildMethodWithParameters'; -import { DeepPartial } from 'src/utils'; -import { NadParameter } from 'src/types/nad'; +import { DeepPartial } from '../../utils'; +import { NadParameter } from '../../types/nad'; const typeVector = describe.each([ ['', 'unknown', 'NSObject*'], diff --git a/packages/builder/src/tests/codegen/oc-types.test.ts b/packages/builder/src/tests/codegen/oc-types.test.ts index aa72879..4478cc0 100644 --- a/packages/builder/src/tests/codegen/oc-types.test.ts +++ b/packages/builder/src/tests/codegen/oc-types.test.ts @@ -98,3 +98,36 @@ test('preserved keyword property name', () => { @end `); }); + +test('chaining extending', () => { + const routes: DeepPartial[] = [{ bean: 'test.Foo', name: 'foo', returnType: 'test.A' }]; + const classes: DeepPartial[] = [ + { name: 'test.A', superclass: 'test.B' }, + { name: 'test.C' }, + { name: 'test.B', superclass: 'test.C' }, + ]; + const code = new Builder({ target: 'oc', base: '', defs: { routes, classes } }).code; + + expect(code).toMatchCode(` + /** + * C + * @JavaClass test.C + */ + @interface C : NSObject + @end + + /** + * B + * @JavaClass test.B + */ + @interface B : C + @end + + /** + * A + * @JavaClass test.A + */ + @interface A : B + @end + `); +}); diff --git a/packages/builder/src/tests/models/Class.test.ts b/packages/builder/src/tests/models/Class.test.ts index 4caf1b4..ce57a2b 100644 --- a/packages/builder/src/tests/models/Class.test.ts +++ b/packages/builder/src/tests/models/Class.test.ts @@ -1,10 +1,9 @@ -import { isJavaUnknown } from 'src/helpers/javaHelper'; -import { Class, Member, Root, Type } from '../../models'; +import { Member, Root, Type, RawClass } from '../../models'; const root = new Root({}); test('generic', () => { - const clz = new Class( + const clz = new RawClass( { name: 'test.MyClass', typeParameters: ['K', 'V'], @@ -14,7 +13,7 @@ test('generic', () => { ], }, root, - ); + ).use(); expect(clz.members[0]).toBeInstanceOf(Member); expect(clz.members[0].name).toBe('key'); @@ -47,24 +46,26 @@ test('generic', () => { }); test('bad class name', () => { - const clz = new Class({ name: '$$..' }, root); + const clz = new RawClass({ name: '$$..' }, root).use(); expect(clz.defName).toBe('UnknownClass'); }); test('bad member name', () => { - const clz = new Class( + const clz = new RawClass( { + name: expect.getState().currentTestName, members: [{ name: '' }, { name: undefined }], }, root, - ); + ).use(); expect(clz.members).toHaveLength(0); }); test('hidden members', () => { - const clz = new Class( + const clz = new RawClass( { + name: expect.getState().currentTestName, members: [ { name: '' }, { name: '~!@#' }, @@ -91,14 +92,15 @@ test('hidden members', () => { ], }, root, - ); + ).use(); expect(clz.members).toHaveLength(0); }); test('JsonNaming SnakeCaseStrategy', () => { - const clz = new Class( + const clz = new RawClass( { + name: expect.getState().currentTestName, annotations: [ { type: 'com.fasterxml.jackson.databind.annotation.JsonNaming', @@ -113,7 +115,7 @@ test('JsonNaming SnakeCaseStrategy', () => { ], }, root, - ); + ).use(); expect(clz.members[0].name).toBe('get_user_info'); expect(clz.members[1].name).toBe('get_user_info_2'); diff --git a/packages/builder/src/tests/models/Enum.test.ts b/packages/builder/src/tests/models/Enum.test.ts index 1d0b64f..7527c25 100644 --- a/packages/builder/src/tests/models/Enum.test.ts +++ b/packages/builder/src/tests/models/Enum.test.ts @@ -1,18 +1,18 @@ -import { Enum, EnumConstant, Root } from '../../models'; +import { RawEnum, EnumConstant, Root } from '../../models'; const root = new Root({}); test('number value', () => { - const obj = new Enum( + const obj = new RawEnum( { - name: 'test.MyEnum', + name: expect.getState().currentTestName, constants: [ { name: 'One', value: 1 }, { name: 'Two', value: 2 }, ], }, root, - ); + ).use(); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('number'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -34,16 +34,16 @@ test('number value', () => { }); test('string value', () => { - const obj = new Enum( + const obj = new RawEnum( { - name: 'test.MyEnum', + name: expect.getState().currentTestName, constants: [ { name: 'One', value: '1' }, { name: 'Two', value: '2' }, ], }, root, - ); + ).use(); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('string'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -65,16 +65,16 @@ test('string value', () => { }); test('mixed value', () => { - const obj = new Enum( + const obj = new RawEnum( { - name: 'test.MyEnum', + name: expect.getState().currentTestName, constants: [ { name: 'One', value: '1' }, { name: 'Two', value: 2 }, ], }, root, - ); + ).use(); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('unknown'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -96,14 +96,14 @@ test('mixed value', () => { }); test('description', () => { - const obj = new Enum( + const obj = new RawEnum( { - name: 'test.MyEnum', + name: expect.getState().currentTestName, annotations: [{ type: 'io.swagger.annotations.ApiModel', attributes: { description: 'hehe' } }], constants: [], }, root, - ); + ).use(); expect(obj).toMatchObject({ constants: [], valueType: 'unknown', diff --git a/packages/builder/src/tests/models/annotations.test.ts b/packages/builder/src/tests/models/annotations.test.ts index 5045923..ebe5bfe 100644 --- a/packages/builder/src/tests/models/annotations.test.ts +++ b/packages/builder/src/tests/models/annotations.test.ts @@ -1,11 +1,11 @@ import { NadAnnotation } from '../../types/nad'; -import { Annotations, Class, Root } from '../../models'; +import { Annotations, Class, RawClass, Root } from '../../models'; import { RequestMappingConditionExpression } from '../../models/annotations/RequestMapping'; const root = new Root({}); test('json', () => { - const MyModel = new Class( + const MyModel = new RawClass( { name: 'test.MyModel', members: [ @@ -32,7 +32,7 @@ test('json', () => { ], }, root, - ); + ).use(); const { members } = MyModel; expect(members).toMatchObject([{ name: 'jackson' }, { name: 'fastjson' }]); diff --git a/packages/builder/src/utils/index.ts b/packages/builder/src/utils/index.ts index 8845f74..c6bc361 100644 --- a/packages/builder/src/utils/index.ts +++ b/packages/builder/src/utils/index.ts @@ -62,3 +62,18 @@ export type DeepPartial = T extends (infer U)[] [P in keyof T]?: DeepPartial; } : T; + +export function assert( + value: T, + message: string = 'The value must be true here', +): asserts value is Exclude { + /* istanbul ignore next */ + if (!value) throw new Error(message); +} + +export const hideProperty = (object: object, propertyName: string) => { + const desc = Object.getOwnPropertyDescriptor(object, propertyName); + assert(desc); + desc.enumerable = false; + Object.defineProperty(object, propertyName, desc); +}; From 3e2d69b33116355745d94dcfc7b01eec3b74c781 Mon Sep 17 00:00:00 2001 From: YanagiEiichi <576398868@qq.com> Date: Thu, 10 Oct 2024 17:43:10 +0800 Subject: [PATCH 4/5] Refactor RawDef --- packages/builder/src/codegen/CodeGenForOc.ts | 2 +- packages/builder/src/codegen/CodeGenForTs.ts | 2 +- packages/builder/src/helpers/tsHelper.ts | 39 ++++-- packages/builder/src/models/Annotated.ts | 2 + packages/builder/src/models/Class.ts | 38 +++--- packages/builder/src/models/DefBase.ts | 19 ++- packages/builder/src/models/Enum.ts | 8 +- packages/builder/src/models/RawDef.ts | 49 ------- packages/builder/src/models/Root.ts | 125 +++++++----------- packages/builder/src/models/Type.ts | 4 +- packages/builder/src/models/index.ts | 1 - .../builder/src/tests/models/Class.test.ts | 20 +-- .../builder/src/tests/models/Enum.test.ts | 18 +-- .../builder/src/tests/models/Root.test.ts | 4 +- .../src/tests/models/annotations.test.ts | 6 +- 15 files changed, 150 insertions(+), 187 deletions(-) delete mode 100644 packages/builder/src/models/RawDef.ts diff --git a/packages/builder/src/codegen/CodeGenForOc.ts b/packages/builder/src/codegen/CodeGenForOc.ts index e4f9484..3b36243 100644 --- a/packages/builder/src/codegen/CodeGenForOc.ts +++ b/packages/builder/src/codegen/CodeGenForOc.ts @@ -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; diff --git a/packages/builder/src/codegen/CodeGenForTs.ts b/packages/builder/src/codegen/CodeGenForTs.ts index ce45fa1..f5d5fac 100644 --- a/packages/builder/src/codegen/CodeGenForTs.ts +++ b/packages/builder/src/codegen/CodeGenForTs.ts @@ -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}`); diff --git a/packages/builder/src/helpers/tsHelper.ts b/packages/builder/src/helpers/tsHelper.ts index 529a068..583a893 100644 --- a/packages/builder/src/helpers/tsHelper.ts +++ b/packages/builder/src/helpers/tsHelper.ts @@ -13,7 +13,7 @@ import { isJavaVoid, isJavaWrapper, } from './javaHelper'; -import { toLowerCamel, Modifier, notEmpty } from '../utils'; +import { toLowerCamel, notEmpty } from '../utils'; import { RootOptions } from '../models/RootOptions'; import { Enum, TypeUsage } from '../models'; @@ -116,14 +116,35 @@ export const t2s = (type: Type | undefined): string => { if (clz instanceof Class) { if (type.usage === TypeUsage.superType) return clzToTs(clz, parameters); - const refs = [ + // 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(([n]) => { - const c = builder.getClass(n.name); - if (!c) return null; + .map(([c]) => { + if (!builder.isUsing(c)) return null; return [c, []] as const; }) .filter(notEmpty), @@ -135,20 +156,20 @@ export const t2s = (type: Type | undefined): string => { // // For example: // - // interface Animal {} + // class Animal {} // class Cat extends Animal {} // class Dog extends Animal {} // // If a method returns Animal, it will be extended to Cat and Dog. // If a method returns Animal, it will be extended to Dog and cannot match Cat. // If a method returns Animal, it will be extended to Cat and Dog. - // + // // But this feature is too complex, just mark this as a TODO temporarily. // - if (refs.length === 0) return clzToTs(clz, parameters); + if (union.length === 0) return clzToTs(clz, parameters); - const ts = refs.map(([c, p]) => clzToTs(c, p)); + const ts = union.map(([c, p]) => clzToTs(c, p)); return buildUnionType(...ts); } diff --git a/packages/builder/src/models/Annotated.ts b/packages/builder/src/models/Annotated.ts index b25501b..670e8db 100644 --- a/packages/builder/src/models/Annotated.ts +++ b/packages/builder/src/models/Annotated.ts @@ -1,3 +1,4 @@ +import { hideProperty } from '../utils'; import { Annotations } from './annotations'; import { u2a, u2o, u2s } from 'u2x'; @@ -9,6 +10,7 @@ export class Annotated; @@ -14,49 +14,55 @@ export class Class extends DefBase { * For example, the value of typeParameters is [ "T", "M" ] for `class Foo {}` */ public readonly typeParameters; - public readonly defName; public readonly description; public readonly modifiers; - private readonly rawClass; + public readonly bounds; - constructor(rawClass: RawClass) { - super(rawClass.raw, rawClass.root); - this.rawClass = rawClass; + constructor(raw: ClassRaw, parent: Root) { + super(raw, parent); this.typeParameters = u2a(this.raw.typeParameters, u2s); - this.modifiers = rawClass.modifiers; - 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 superclass() { + public get superclass() { const value = Type.create(u2s(this.raw.superclass), this, TypeUsage.superType); Object.defineProperty(this, 'superclass', { configurable: true, value }); return value; } public hasConstructor() { - return this.rawClass.hasConstructor(); + const { modifiers } = this; + if (modifiers === undefined) return true; + return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); } public spread() { - const { superclass, members, name, builder } = this; + 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()) rawClz.use(); + if (rawClz.hasConstructor()) builder.touchDef(rawClz); } } } diff --git a/packages/builder/src/models/DefBase.ts b/packages/builder/src/models/DefBase.ts index 18a1d1d..75ccb32 100644 --- a/packages/builder/src/models/DefBase.ts +++ b/packages/builder/src/models/DefBase.ts @@ -1,15 +1,22 @@ +import { hideProperty } from '../utils'; import { AnnocatedRaw, Annotated } from './Annotated'; import type { Root } from './Root'; export class DefBase extends Annotated { - 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; } } diff --git a/packages/builder/src/models/Enum.ts b/packages/builder/src/models/Enum.ts index e96473d..73aedce 100644 --- a/packages/builder/src/models/Enum.ts +++ b/packages/builder/src/models/Enum.ts @@ -3,7 +3,7 @@ import { u2a } from 'u2x'; import { DefBase } from './DefBase'; import { EnumConstant } from './EnumConstant'; import { NadEnum } from '../types/nad'; -import { RawEnum } from './RawDef'; +import { Root } from './Root'; type EnumRaw = Dubious; @@ -11,7 +11,7 @@ export class Enum extends DefBase { public readonly constants; public readonly valueType; public readonly description; - constructor({ raw, root }: RawEnum) { + constructor(raw: EnumRaw, root: Root) { super(raw, root); this.constants = u2a(this.raw.constants, (i) => new EnumConstant(i, this)); this.valueType = this.initValueType(); @@ -25,4 +25,8 @@ export class Enum extends DefBase { if (vt !== 'string' && vt !== 'number') vt = 'unknown' as const; return vt; } + + public spread() { + // noop + } } diff --git a/packages/builder/src/models/RawDef.ts b/packages/builder/src/models/RawDef.ts deleted file mode 100644 index 7204f40..0000000 --- a/packages/builder/src/models/RawDef.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { hideProperty, Modifier, notEmpty } from '../utils'; -import { u2o, u2s, u2a, u2n } from 'u2x'; -import { Root } from './Root'; - -abstract class RawDef { - public readonly raw; - public readonly root; - public readonly name; - constructor(raw: unknown, root: Root) { - this.root = root; - this.raw = u2o(raw); - this.name = u2s(this.raw.name) ?? ''; - hideProperty(this, 'raw'); - hideProperty(this, 'root'); - } -} - -export class RawClass extends RawDef { - public readonly superclass; - public readonly interfaces; - public readonly modifiers; - - constructor(raw: unknown, root: Root) { - super(raw, root); - this.superclass = u2s(this.raw.superclass) ?? null; - this.interfaces = u2a(this.raw.interfaces, (u) => u2s(u)).filter(notEmpty); - this.modifiers = u2n(this.raw.modifiers); - } - - public hasConstructor() { - const {modifiers} = this; - if (modifiers === undefined) return true; - return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers); - } - - public use() { - return this.root.touchClass(this); - } -} - -export class RawEnum extends RawDef { - constructor(raw: unknown, root: Root) { - super(raw, root); - } - - public use() { - return this.root.touchEnum(this); - } -} diff --git a/packages/builder/src/models/Root.ts b/packages/builder/src/models/Root.ts index c96c206..804b48e 100644 --- a/packages/builder/src/models/Root.ts +++ b/packages/builder/src/models/Root.ts @@ -1,33 +1,22 @@ import { Module } from './Module'; import { Class } from './Class'; -import { - computeIfAbsent, - Dubious, - toUpperCamel, - parseDsv, - UniqueName, - removeDynamicSuffix, - Modifier, - assert, -} from '../utils'; -import { u2o, u2a, u2s, u2n } from 'u2x'; +import { computeIfAbsent, Dubious, toUpperCamel, parseDsv, UniqueName, removeDynamicSuffix, assert } from '../utils'; +import { u2o, u2a, u2s } from 'u2x'; import { Enum } from './Enum'; import { NadResult } from '../types/nad'; import { RouteRaw } from './Route'; import { RootOptions } from './RootOptions'; import { CommonDefs } from './CommonDefs'; -import { RawClass, RawEnum } from './RawDef'; export type RawDefs = Dubious; export class Root { - private readonly rawClassMap; + private readonly classMap; private readonly derivationMap; - private readonly rawEnumMap; + private readonly enumMap; private readonly rawModuleMap; - private readonly classes: Record; - private readonly enums: Record; + private readonly activeDefs; public readonly options; @@ -40,13 +29,13 @@ export class Root { constructor(raw: RawDefs, options: Partial = {}) { this.options = new RootOptions(options); - this.rawClassMap = new Map(u2a(raw.classes, (i) => new RawClass(i, this)).map((d) => [d.name, d] as const)); + this.enumMap = new Map(u2a(raw.enums, (i) => new Enum(i, this)).map((d) => [d.name, d] as const)); + this.classMap = new Map(u2a(raw.classes, (i) => new Class(i, this)).map((d) => [d.name, d] as const)); this.derivationMap = this.buildDerivationMap(); - this.rawEnumMap = new Map(u2a(raw.enums, (i) => new RawEnum(i, this)).map((d) => [d.name, d] as const)); this.rawModuleMap = new Map(u2a(raw.modules, (i) => [u2s(u2o(i).name), i])); - this.classes = Object.create(null); - this.enums = Object.create(null); + this.activeDefs = new Set(); + this.commonDefs = new CommonDefs(); this.uniqueNameSeparator = options.uniqueNameSeparator; @@ -74,27 +63,27 @@ export class Root { } private buildDerivationMap() { - const { rawClassMap } = this; + const { classMap: rawClassMap } = this; // For example: - // If the classes defining like follow Java code // + // If the classes defining like this: + // ```java // class Animal {} // class Dog extends Animal {} // class Cat extends Animal {} + // ``` // // The map result will be: // - // { 'Animal': { 'Dog': 'Animal', 'Cat': 'Animal' } } + // { 'Animal': { Dog => 'Animal', Cat => 'Animal' } } // - const map = new Map>(); + const map = new Map>(); for (const [, clz] of rawClassMap) { - const { superclass, interfaces } = clz; - for (const n of [superclass, ...u2a(interfaces)]) { - if (!n) continue; + for (const n of clz.bounds) { const sn = n.replace(/\<.*/, ''); if (!rawClassMap.has(sn)) continue; - const item = computeIfAbsent(map, n, () => new Map()); + const item = computeIfAbsent(map, n, () => new Map()); item.set(clz, n); } } @@ -102,67 +91,51 @@ export class Root { return map; } - get declarationList() { - return Object.values(this.classes); + public get classList() { + return [...this.activeDefs].filter((i): i is Class => i instanceof Class); } - get enumList() { - return Object.values(this.enums); + public get enumList() { + return [...this.activeDefs].filter((i): i is Enum => i instanceof Enum); } - get moduleCount() { + public get moduleCount() { return this.modules.length; } - get defCount() { - return Object.keys(this.classes).length; - } - get apiCount() { - return this.modules.reduce((s, i) => s + i.routes.length, 0); - } - public getRawDef(name: string) { - const { rawClassMap, rawEnumMap } = this; - return rawClassMap.get(name) || rawEnumMap.get(name) || null; - } - - /** - * This method can only be called from RawEnum. - */ - public touchEnum(raw: RawEnum) { - const { enums } = this; - if (raw.name in enums) return enums[raw.name]; - const en = new Enum(raw); - enums[raw.name] = en; - return en; + public get defCount() { + return this.activeDefs.size; } - /** - * This method can only be called from RawClass. - */ - public touchClass(raw: RawClass) { - const { classes } = this; - if (raw.name in classes) return classes[raw.name]; - const clz = new Class(raw); - classes[raw.name] = clz; - clz.spread(); - return clz; + public get apiCount() { + return this.modules.reduce((s, i) => s + i.routes.length, 0); } /** * This method can only be called from Type. */ - public touchDef(name: string): Enum | Class | null { - const raw = this.getRawDef(name); - if (raw instanceof RawClass) return this.touchClass(raw); - if (raw instanceof RawEnum) return this.touchEnum(raw); - this.unknownTypes.add(name); + public touchDef(name: string): Enum | Class | null; + public touchDef(def: T): T; + public touchDef(what: string | Enum | Class): Enum | Class | null { + if (typeof what === 'string') { + const { classMap, enumMap } = this; + what = classMap.get(what) || enumMap.get(what) || what; + } + + if (what instanceof Enum || what instanceof Class) { + const { activeDefs } = this; + if (activeDefs.has(what)) return what; + activeDefs.add(what); + what.spread(); + return what; + } + + this.unknownTypes.add(what); return null; } - public getClass(name: string) { - const { classes } = this; - if (name in classes) return classes[name]; - return null; + public isUsing(def: Enum | Class) { + return this.activeDefs.has(def); } public takeUniqueName(javaClassPath: string, fixFunction: (s: string) => string) { @@ -177,11 +150,11 @@ export class Root { return UniqueName.createFor(this, fixFunction(name), uniqueNameSeparator); } - public *findDerivativedRefs(name: string, depth = 0): Iterable<[RawClass, string]> { + public *findDerivativedRefs(name: string, depth = 0): Iterable<[Class, string]> { assert(depth < 20, 'Too many nested derivative class: ' + name); - for (const [who, usage] of this.derivationMap.get(name) ?? []) { - yield [who, usage]; - yield* this.findDerivativedRefs(who.name, depth + 1); + for (const [clz, usage] of this.derivationMap.get(name) ?? []) { + yield [clz, usage]; + yield* this.findDerivativedRefs(clz.name, depth + 1); } } } diff --git a/packages/builder/src/models/Type.ts b/packages/builder/src/models/Type.ts index 795bca7..f461f22 100644 --- a/packages/builder/src/models/Type.ts +++ b/packages/builder/src/models/Type.ts @@ -7,7 +7,7 @@ import type { Root } from './Root'; export type TypeOwner = Class | Root; const getBuilderFromOwner = (owner: TypeOwner) => { - if ('builder' in owner) return owner.builder; + if ('root' in owner) return owner.root; return owner; }; @@ -48,7 +48,7 @@ export class Type { } } - get owner() { + private get owner() { const owner = wm.get(this); /* istanbul ignore next */ if (!owner) throw neverReachHere(); diff --git a/packages/builder/src/models/index.ts b/packages/builder/src/models/index.ts index 45af3ea..f2e9311 100644 --- a/packages/builder/src/models/index.ts +++ b/packages/builder/src/models/index.ts @@ -10,4 +10,3 @@ export * from './Parameter'; export * from './Root'; export * from './Route'; export * from './Type'; -export * from './RawDef'; diff --git a/packages/builder/src/tests/models/Class.test.ts b/packages/builder/src/tests/models/Class.test.ts index ce57a2b..989bd70 100644 --- a/packages/builder/src/tests/models/Class.test.ts +++ b/packages/builder/src/tests/models/Class.test.ts @@ -1,9 +1,9 @@ -import { Member, Root, Type, RawClass } from '../../models'; +import { Member, Root, Type, Class } from '../../models'; const root = new Root({}); test('generic', () => { - const clz = new RawClass( + const clz = new Class( { name: 'test.MyClass', typeParameters: ['K', 'V'], @@ -13,7 +13,7 @@ test('generic', () => { ], }, root, - ).use(); + ); expect(clz.members[0]).toBeInstanceOf(Member); expect(clz.members[0].name).toBe('key'); @@ -46,24 +46,24 @@ test('generic', () => { }); test('bad class name', () => { - const clz = new RawClass({ name: '$$..' }, root).use(); + const clz = new Class({ name: '$$..' }, root); expect(clz.defName).toBe('UnknownClass'); }); test('bad member name', () => { - const clz = new RawClass( + const clz = new Class( { name: expect.getState().currentTestName, members: [{ name: '' }, { name: undefined }], }, root, - ).use(); + ); expect(clz.members).toHaveLength(0); }); test('hidden members', () => { - const clz = new RawClass( + const clz = new Class( { name: expect.getState().currentTestName, members: [ @@ -92,13 +92,13 @@ test('hidden members', () => { ], }, root, - ).use(); + ); expect(clz.members).toHaveLength(0); }); test('JsonNaming SnakeCaseStrategy', () => { - const clz = new RawClass( + const clz = new Class( { name: expect.getState().currentTestName, annotations: [ @@ -115,7 +115,7 @@ test('JsonNaming SnakeCaseStrategy', () => { ], }, root, - ).use(); + ); expect(clz.members[0].name).toBe('get_user_info'); expect(clz.members[1].name).toBe('get_user_info_2'); diff --git a/packages/builder/src/tests/models/Enum.test.ts b/packages/builder/src/tests/models/Enum.test.ts index 7527c25..687b85c 100644 --- a/packages/builder/src/tests/models/Enum.test.ts +++ b/packages/builder/src/tests/models/Enum.test.ts @@ -1,9 +1,9 @@ -import { RawEnum, EnumConstant, Root } from '../../models'; +import { Enum, EnumConstant, Root } from '../../models'; const root = new Root({}); test('number value', () => { - const obj = new RawEnum( + const obj = new Enum( { name: expect.getState().currentTestName, constants: [ @@ -12,7 +12,7 @@ test('number value', () => { ], }, root, - ).use(); + ); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('number'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -34,7 +34,7 @@ test('number value', () => { }); test('string value', () => { - const obj = new RawEnum( + const obj = new Enum( { name: expect.getState().currentTestName, constants: [ @@ -43,7 +43,7 @@ test('string value', () => { ], }, root, - ).use(); + ); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('string'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -65,7 +65,7 @@ test('string value', () => { }); test('mixed value', () => { - const obj = new RawEnum( + const obj = new Enum( { name: expect.getState().currentTestName, constants: [ @@ -74,7 +74,7 @@ test('mixed value', () => { ], }, root, - ).use(); + ); expect(obj.constants.length).toBe(2); expect(obj.valueType).toBe('unknown'); expect(obj.constants[0]).toBeInstanceOf(EnumConstant); @@ -96,14 +96,14 @@ test('mixed value', () => { }); test('description', () => { - const obj = new RawEnum( + const obj = new Enum( { name: expect.getState().currentTestName, annotations: [{ type: 'io.swagger.annotations.ApiModel', attributes: { description: 'hehe' } }], constants: [], }, root, - ).use(); + ); expect(obj).toMatchObject({ constants: [], valueType: 'unknown', diff --git a/packages/builder/src/tests/models/Root.test.ts b/packages/builder/src/tests/models/Root.test.ts index 8502516..2e25c72 100644 --- a/packages/builder/src/tests/models/Root.test.ts +++ b/packages/builder/src/tests/models/Root.test.ts @@ -69,7 +69,7 @@ test('defs', () => { const getDefBySimpleName = (root: Root, name: string): Enum | Class | null => { return ( - root.declarationList.find((def) => def.simpleName === name) || + root.classList.find((def) => def.simpleName === name) || root.enumList.find((def) => def.simpleName === name) || null ); @@ -102,5 +102,5 @@ test('typeMapping', () => { name: 'java.util.Map', parameters: [{ name: 'java.lang.Long' }, { name: 'java.lang.String' }], }); - expect(root.declarationList).toHaveLength(0); + expect(root.classList).toHaveLength(0); }); diff --git a/packages/builder/src/tests/models/annotations.test.ts b/packages/builder/src/tests/models/annotations.test.ts index ebe5bfe..5045923 100644 --- a/packages/builder/src/tests/models/annotations.test.ts +++ b/packages/builder/src/tests/models/annotations.test.ts @@ -1,11 +1,11 @@ import { NadAnnotation } from '../../types/nad'; -import { Annotations, Class, RawClass, Root } from '../../models'; +import { Annotations, Class, Root } from '../../models'; import { RequestMappingConditionExpression } from '../../models/annotations/RequestMapping'; const root = new Root({}); test('json', () => { - const MyModel = new RawClass( + const MyModel = new Class( { name: 'test.MyModel', members: [ @@ -32,7 +32,7 @@ test('json', () => { ], }, root, - ).use(); + ); const { members } = MyModel; expect(members).toMatchObject([{ name: 'jackson' }, { name: 'fastjson' }]); From e91916d12c6e8da5208b8bf596fd9d3a8ed79ccf Mon Sep 17 00:00:00 2001 From: YanagiEiichi <576398868@qq.com> Date: Fri, 11 Oct 2024 09:37:11 +0800 Subject: [PATCH 5/5] Release @huolala-tech/nad-builder@1.1.3 --- packages/builder/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/package.json b/packages/builder/package.json index 72e17cb..235c623 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -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",