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 1.1.1 #21

Merged
merged 14 commits into from
Jul 10, 2024
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@huolala-tech/custom-error": "^1.0.0",
"@huolala-tech/hooks": "^1.0.0",
"@huolala-tech/nad-builder": "^1.0.4",
"@huolala-tech/request": "^1.1.3",
"@huolala-tech/request": "^1.1.4",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
Expand Down
4 changes: 0 additions & 4 deletions packages/builder/jest.config.js

This file was deleted.

5 changes: 5 additions & 0 deletions packages/builder/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
preset: 'ts-jest',
testRegex: '\\.test\\.ts$',
setupFilesAfterEnv: ['./src/tests/jest.setup.ts'],
};
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.0",
"version": "1.1.1",
"description": "Convert the Java AST to client-side code",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
7 changes: 5 additions & 2 deletions packages/builder/src/codegen/CodeGenForTs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class CodeGenForTs extends CodeGen {
.map((p) => {
// If a parameter is optional and has any required parameters in the right,
// then change its type to `T | null` and make it requreid.
if (hasRequired && p.required === '?') return `${p.name}: ${t2s(p.type)} | null`;
if (hasRequired && p.required === '?') return `${p.name}: ${t2s(p.type)} | undefined`;
// If current parameter is required, update `hasRequired` flag to `true`.
hasRequired = hasRequired || p.required === '';
// Otherwise, the current parameter is requreid, or there are no required parameters to its right,
Expand All @@ -103,6 +103,7 @@ export class CodeGenForTs extends CodeGen {
for (const p of a.parameters) {
if (p.description) this.write(`@param ${p.name} ${p.description}`);
}
if (a.deprecated) this.write('@deprecated');
});
this.write(`async ${a.uniqName}(${pars.join(', ')}) {`);
this.writeBlock(() => {
Expand Down Expand Up @@ -138,6 +139,7 @@ export class CodeGenForTs extends CodeGen {
this.writeComment(() => {
this.write(m.description || m.moduleName);
this.write(`@iface ${m.name}`);
if (m.deprecated) this.write(`@deprecated`);
});
this.write(`export const ${m.moduleName} = {`);
this.writeBlock(() => {
Expand Down Expand Up @@ -203,9 +205,10 @@ export class CodeGenForTs extends CodeGen {
this.write(`export interface ${defStr} {`);
this.writeBlock(() => {
for (const m of c.members) {
if (m.description) {
if (m.description || m.deprecated) {
this.writeComment(() => {
this.write(m.description);
if (m.deprecated) this.write('@deprecated');
});
}
this.write(`${m.name}${m.optional}: ${t2s(m.type)};`);
Expand Down
2 changes: 2 additions & 0 deletions packages/builder/src/helpers/javaHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ export const isJavaPrimitiveTypes = [
export const isJavaStringTypes = [
'java.lang.String',
'java.lang.StringBuffer',

'java.time.LocalDateTime',
'java.time.LocalDate',
'java.time.OffsetDateTime',
'java.time.OffsetTime',
'java.time.ZonedDateTime',
'java.time.LocalTime',

'java.lang.Class',
'java.net.URL',
'java.net.URI',
Expand Down
8 changes: 4 additions & 4 deletions packages/builder/src/helpers/tsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ export const t2s = (type: Type): string => {

switch (name) {
case 'java.math.BigDecimal':
builder.commonDefs.BigDecimal = 'string | number';
builder.commonDefs.BigDecimal = '`${number}` | number';
return 'BigDecimal';
case 'java.math.BigInteger':
builder.commonDefs.BigInteger = 'string | number';
builder.commonDefs.BigInteger = '`${number}` | number';
return 'BigInteger';
case 'org.springframework.web.multipart.MultipartFile':
builder.commonDefs.MultipartFile = 'Blob | File | string';
return 'MultipartFile';
default:
}
if (isJavaLong(name)) {
builder.commonDefs.Long = 'string | number';
builder.commonDefs.Long = '`${number}` | number';
return 'Long';
}
if (isJavaNumber(name)) return 'number';
Expand All @@ -67,7 +67,7 @@ export const t2s = (type: Type): string => {
} else {
keyType = 'PropertyKey';
}
return `Record<${keyType}, ${t2s(second)}>`;
return `Record<${keyType}, ${t2s(second)} | undefined>`;
}
if (isJavaList(name)) {
return `${t2s(parameters[0])}[]`;
Expand Down
24 changes: 19 additions & 5 deletions packages/builder/src/models/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { u2a, u2o, u2s } from 'u2x';
import { Annotations } from './annotations';
import type { Class } from './Class';
import { Type } from './Type';
import { Dubious, notEmpty, toLowerCamel } from '../utils';
import { Dubious, notEmpty, toSnake } from '../utils';
import { NadMember } from '../types/nad';

export class Member {
Expand All @@ -13,14 +13,26 @@ export class Member {
public readonly description;
public readonly visible: boolean;
public readonly optional: '' | '?';
public readonly deprecated;
constructor(
raw: Dubious<NadMember>,
public readonly owner: Class,
) {
const { name, type, annotations } = raw;
this.annotations = Annotations.create(u2a(annotations).filter(notEmpty).map(u2o).flat());
this.name = owner.options.fixPropertyName(u2s(this.annotations.json.alias || name) || '');
this.type = Type.create(u2s(type), owner);
this.annotations = Annotations.create(u2a(raw.annotations).filter(notEmpty).map(u2o).flat());

const { fixPropertyName } = owner.options;
if (this.annotations.json.alias) {
this.name = fixPropertyName(this.annotations.json.alias);
} else {
const rawName = u2s(raw.name) ?? '';
if (owner.annotations.json.needToSnake) {
this.name = fixPropertyName(toSnake(rawName));
} else {
this.name = fixPropertyName(rawName);
}
}

this.type = Type.create(u2s(raw.type), owner);
const amp = this.annotations.swagger.getApiModelProperty();
this.description = amp?.value;

Expand All @@ -42,5 +54,7 @@ export class Member {
// It is optinoal by default unless set to @NotNull or @ApiModelProperty(required = true) or JavaPrimitive types.
this.optional =
amp?.required === true || this.annotations.hasNonNull() || isJavaPrimitive(this.type.name) ? '' : '?';

this.deprecated = this.annotations.hasDeprecated();
}
}
2 changes: 2 additions & 0 deletions packages/builder/src/models/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class Module extends Annotated<ModuleRaw> {
public readonly moduleName;
public readonly routes;
public readonly description;
public readonly deprecated;
constructor(raw: ModuleRaw, builder: Root, list: RouteRaw[]) {
super(raw);
this.builder = builder;
Expand Down Expand Up @@ -51,5 +52,6 @@ export class Module extends Annotated<ModuleRaw> {
this.routes = sList.map((o) => new Route(o, this));

this.description = this.annotations.swagger.getApi()?.value || '';
this.deprecated = this.annotations.hasDeprecated();
}
}
16 changes: 9 additions & 7 deletions packages/builder/src/models/Parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ export class Parameter extends Annotated<Dubious<NadParameter>> {
// 2. This parameter has any "NotNull" annotations (contains @NotNull, @NonNull, and @Nonnull).
// 3. The type of this parameter is a java primitive type.
this.required =
ap?.required ||
rp?.required ||
pv?.required ||
rb?.required ||
rh?.required ||
this.annotations.hasNonNull() ||
isJavaPrimitive(this.type.name)
(ap?.required ||
rp?.required ||
pv?.required ||
rb?.required ||
rh?.required ||
this.annotations.hasNonNull() ||
isJavaPrimitive(this.type.name)) &&
// In fact, a parameter can be optional if a default value is provided.
rp?.defaultValue === undefined
? ('' as const)
: ('?' as const);

Expand Down
2 changes: 2 additions & 0 deletions packages/builder/src/models/Route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class Route extends Annotated<RouteRaw> {
public readonly customFlags;
public readonly requiredHeaders: [string, string][];
public readonly requiredParams: [string, string][];
public readonly deprecated;

constructor(raw: RouteRaw | undefined, module: Module) {
super(raw);
Expand Down Expand Up @@ -55,6 +56,7 @@ export class Route extends Annotated<RouteRaw> {

this.parameters = u2a(this.raw.parameters, (i) => Parameter.create(i, this)).filter(notEmpty);
this.description = this.annotations.swagger.getApiOperation()?.description || '';
this.deprecated = this.annotations.hasDeprecated();

this.requiredHeaders = [];
this.requiredParams = [];
Expand Down
12 changes: 12 additions & 0 deletions packages/builder/src/models/annotations/JsonAnnotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export interface JSONField {
serialize: boolean;
}

export interface JsonNaming {
value: string;
}

export class JsonAnnotations {
private annotations;
constructor(annotations: Annotations) {
Expand All @@ -29,6 +33,10 @@ export class JsonAnnotations {
return this.getJsonIgnore()?.value === true || this.getJSONField()?.serialize === false;
}

public get needToSnake() {
return this.getJsonNaming()?.value === 'com.fasterxml.jackson.databind.PropertyNamingStrategy$SnakeCaseStrategy';
}

private getJsonProperty() {
return this.annotations.find<JsonProperty>('com.fasterxml.jackson.annotation.JsonProperty');
}
Expand All @@ -40,4 +48,8 @@ export class JsonAnnotations {
private getJSONField() {
return this.annotations.find<JSONField>('com.alibaba.fastjson.annotation.JSONField');
}

private getJsonNaming() {
return this.annotations.find<JsonNaming>('com.fasterxml.jackson.databind.annotation.JsonNaming');
}
}
8 changes: 7 additions & 1 deletion packages/builder/src/models/annotations/WebAnnocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ export abstract class ValueAliasName extends AnnotationBase<string> {
* @see https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
*/
export class RequestParam extends ValueAliasName {
public static iface = 'org.springframework.web.bind.annotation.RequestParam';
public static readonly iface = 'org.springframework.web.bind.annotation.RequestParam';
public static readonly DEFAULT_VALUE = '\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n';
get defaultValue() {
const value = u2s(this.raw.defaultValue);
if (value === RequestParam.DEFAULT_VALUE) return undefined;
return value;
}
get required() {
return u2b(this.raw.required) ?? true;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/builder/src/models/annotations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export class Annotations {
return false;
}

hasDeprecated() {
if (this.find<NotNull>('java.lang.Deprecated')) return true;
return false;
}

find<T = Record<string, unknown>>(name: string, endsWith = false): T | null {
if (!endsWith) return (this.map.get(name) as T) || null;
for (const [key, value] of this.map) {
Expand Down
2 changes: 1 addition & 1 deletion packages/builder/src/tests/codegen/all-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const typeVector = describe.each([
['java.util.List', 'unknown[]', 'NSArray<NSObject*>*'],
['java.util.List<java.lang.Long>', 'Long[]', 'NSArray<NSNumber*>*'],
['java.util.List<java.lang.Void>', 'void[]', 'NSArray<void*>*'],
['java.util.Map<java.lang.Long, java.lang.Long>', 'Record<Long, Long>', 'NSDictionary*'],
['java.util.Map<java.lang.Long, java.lang.Long>', 'Record<Long, Long | undefined>', 'NSDictionary*'],
['groovy.lang.Tuple2<java.lang.String, java.lang.Long>', '[ string, Long ]', 'NSArray<NSObject*>*'],
['java.util.List<java.lang.ThreadLocal<java.lang.Long>>', 'Optional<Long>[]', 'NSArray<NSNumber*>*' ],
['java.util.List<java.util.Optional<java.lang.Long>>', 'Optional<Long>[]', 'NSArray<NSNumber*>*' ],
Expand Down
11 changes: 5 additions & 6 deletions packages/builder/src/tests/codegen/oc-enum.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NadEnum, NadEnumConstant, NadRoute } from '../../types/nad';
import { Builder } from '../../Builder';
import { mg } from '../test-tools/mg';
import { DeepPartial } from '../../utils';

const config = { base: 'test', target: 'oc' } as const;
Expand All @@ -21,12 +20,12 @@ const buildEnum = (...constants: Partial<NadEnumConstant>[]) => {
return new Builder({
...config,
defs: { routes: [foo], enums: [MyType] },
}).code.replace(/\s+/g, ' ');
}).code;
};

test('number enum', () => {
const code = buildEnum({ name: 'WATER', value: 1 }, { name: 'FIRE', value: 2 });
expect(code).toContain(mg`
expect(code).toMatchCode(`
typedef NSNumber MyType;
const MyType *MyType_WATER = @1;
const MyType *MyType_FIRE = @2;
Expand All @@ -35,7 +34,7 @@ test('number enum', () => {

test('string enum', () => {
const code = buildEnum({ name: 'WATER', value: 'water' }, { name: 'FIRE', value: 'fire' });
expect(code).toContain(mg`
expect(code).toMatchCode(`
typedef NSString MyType;
const MyType *MyType_WATER = @"water";
const MyType *MyType_FIRE = @"fire";
Expand All @@ -44,7 +43,7 @@ test('string enum', () => {

test('mixed enum', () => {
const code = buildEnum({ name: 'WATER', value: 'water' }, { name: 'FIRE', value: 1 });
expect(code).toContain(mg`
expect(code).toMatchCode(`
typedef NSObject MyType;
const MyType *MyType_WATER = @"WATER";
const MyType *MyType_FIRE = @"FIRE";
Expand All @@ -53,7 +52,7 @@ test('mixed enum', () => {

test('enum string includes spetial characters', () => {
const code = buildEnum({ name: 'WATER', value: 'wa\nter' }, { name: 'FIRE', value: 'fi\\re' });
expect(code).toContain(mg`
expect(code).toMatchCode(`
typedef NSString MyType;
const MyType *MyType_WATER = @"wa\\x0ater";
const MyType *MyType_FIRE = @"fi\\x5cre";
Expand Down
15 changes: 8 additions & 7 deletions packages/builder/src/tests/codegen/oc-swagger.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Builder } from '../../Builder';
import { mg } from '../test-tools/mg';
import { swaggerTestDefs } from '../defs/swaggerTestDefs';

const code = new Builder({ target: 'oc', base: '', defs: swaggerTestDefs }).code.replace(/\s+/g, ' ');
const code = new Builder({ target: 'oc', base: '', defs: swaggerTestDefs }).code;

test('module', () => {
expect(code).toContain(mg`
expect(code).toMatchCode(`
/**
* My Module
* @JavaClass test.Demo
Expand All @@ -15,7 +14,7 @@ test('module', () => {
});

test('route', () => {
expect(code).toContain(mg`
expect(code).toMatchCode(`
/**
* My Route
* @param a My A
Expand All @@ -27,20 +26,22 @@ test('route', () => {
});

test('class', () => {
expect(code).toContain(mg`
expect(code).toMatchCode(`
/**
* My Model
* @JavaClass test.FooModel
*/
@interface FooModel : NSObject
/** * My Field */
/**
* My Field
*/
@property (nonatomic, assign) FooEnum *type;
@end
`);
});

test('enum', () => {
expect(code).toContain(mg`
expect(code).toMatchCode(`
/**
* My Enum
* @JavaClass test.FooEnum
Expand Down
5 changes: 2 additions & 3 deletions packages/builder/src/tests/codegen/oc-tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NadClass, NadRoute } from '../../types/nad';
import { Builder } from '../../Builder';
import { mg } from '../test-tools/mg';
import { DeepPartial } from '../../utils';

const config = { base: 'test', target: 'oc' } as const;
Expand All @@ -23,9 +22,9 @@ test('Tree', () => {
const code = new Builder({
...config,
defs: { routes: [getTree], classes: [Node] },
}).code.replace(/\s+/g, ' ');
}).code;

expect(code).toContain(mg`
expect(code).toMatchCode(`
@interface Node : NSObject
@property (nonatomic, assign) Node *parent;
@property (nonatomic, assign) NSArray<Node*> *children;
Expand Down
Loading
Loading