Skip to content

Commit f92b739

Browse files
committed
Parse simple_components.json into type-safe ComponentDescriptors
1 parent db72b8f commit f92b739

File tree

5 files changed

+133
-52
lines changed

5 files changed

+133
-52
lines changed

src/ComponentMetadata.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { YAIL_TYPES, type YailType, type Component, type EventMetadata, type ComponentMetadata as IComponentMetadata, type MethodMetadata, type PropertyMetadata, type ComponentDescriptorJson } from './types.js'
21
import type { Environment } from './Environment.js'
2+
import { YAIL_TYPES, type Component, type EventMetadata, type ComponentMetadata as IComponentMetadata, type MethodMetadata, type PropertyMetadata, type YailType } from './types.js'
3+
import type { ComponentDescriptor } from './validators/component-descriptor.js'
34

45
interface ValidationResult {
56
valid: boolean
@@ -23,13 +24,13 @@ interface ComponentStatistics {
2324
}
2425

2526
class ComponentMetadata implements IComponentMetadata {
26-
private componentsData: ComponentDescriptorJson[]
27+
private componentsData: ComponentDescriptor[]
2728
private supportedComponents: Set<string>
2829

2930
constructor(private environment: Environment) {
3031
// Use the environment's component descriptors directly
3132
this.componentsData = this.environment.componentDescriptors
32-
33+
3334
// Create supported components set from the environment data
3435
this.supportedComponents = new Set(
3536
this.componentsData.map(comp => comp.type)
@@ -61,7 +62,7 @@ class ComponentMetadata implements IComponentMetadata {
6162
return results
6263
}
6364

64-
private getComponent(componentType: string): ComponentDescriptorJson | null {
65+
private getComponent(componentType: string): ComponentDescriptor | null {
6566
const fullType = componentType.includes('.')
6667
? componentType
6768
: `com.google.appinventor.components.runtime.${componentType}`

src/Environment.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
1-
import type {
2-
ComponentDescriptorJson,
3-
ExtensionDescriptorJson,
4-
} from "./types.js";
1+
import type { ExtensionDescriptorJson } from "./types.js";
2+
import { type ComponentDescriptor, type ComponentDescriptors, ComponentDescriptorsSchema } from "./validators/component-descriptor.js";
53

64
export class Environment {
75
constructor(
86
public readonly name: string,
9-
public readonly componentDescriptors: ComponentDescriptorJson[],
10-
) {}
7+
public readonly componentDescriptors: ComponentDescriptors,
8+
) { }
119

1210
isComponentSupported(componentName: string): boolean {
1311
return this.componentDescriptors.some(
1412
(comp) => comp.name === componentName,
1513
);
1614
}
1715

18-
getComponentDescriptor(
19-
componentType: string,
20-
): ComponentDescriptorJson | null {
16+
getComponentDescriptor(componentType: string): ComponentDescriptor | null {
2117
return (
22-
this.componentDescriptors.find((comp) => comp.type === componentType) ||
18+
this.componentDescriptors.find((comp) => comp.type === componentType) ??
2319
null
2420
);
2521
}
@@ -35,28 +31,40 @@ export class Environment {
3531
}
3632

3733
static async kodularCreator(): Promise<Environment> {
34+
const environmentName = "Kodular Creator";
3835
const simpleComponentsJSON = (
3936
await import("./environments/kodular-creator/simple_components.json", {
4037
with: { type: "json" },
4138
})
4239
).default;
43-
// TODO: Use Zod to validate the JSON structure
44-
return new Environment(
45-
"Kodular Creator",
46-
simpleComponentsJSON as ComponentDescriptorJson[],
47-
);
40+
41+
// Validate the JSON structure using Zod
42+
const validationResult = await ComponentDescriptorsSchema.safeParseAsync(simpleComponentsJSON);
43+
if (!validationResult.success) {
44+
throw new Error(
45+
`Invalid component descriptor JSON for ${environmentName}: ${validationResult.error.message}`
46+
);
47+
}
48+
49+
return new Environment(environmentName, validationResult.data);
4850
}
4951

5052
static async mitAppInventor(): Promise<Environment> {
53+
const environmentName = "MIT App Inventor";
5154
const simpleComponentsJSON = (
5255
await import("./environments/mit-app-inventor/simple_components.json", {
5356
with: { type: "json" },
5457
})
5558
).default;
56-
// TODO: Use Zod to validate the JSON structure
57-
return new Environment(
58-
"MIT App Inventor",
59-
simpleComponentsJSON as ComponentDescriptorJson[],
60-
);
59+
60+
// Validate the JSON structure using Zod
61+
const validationResult = await ComponentDescriptorsSchema.safeParseAsync(simpleComponentsJSON);
62+
if (!validationResult.success) {
63+
throw new Error(
64+
`Invalid component descriptor JSON for ${environmentName}: ${validationResult.error.message}`
65+
);
66+
}
67+
68+
return new Environment(environmentName, validationResult.data);
6169
}
6270
}

src/types.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,6 @@ export interface BkyStatistics {
9090
eventHandlers: number
9191
}
9292

93-
export interface ComponentDescriptorJson {
94-
type: string;
95-
name: string;
96-
external: "true" | "false";
97-
version: string;
98-
dateBuilt: string;
99-
categoryString: string;
100-
helpString: string;
101-
helpUrl: string;
102-
showOnPalette: "true" | "false";
103-
nonVisible: "true" | "false";
104-
iconName: string;
105-
androidMinSdk: string | number;
106-
properties: ComponentDescriptorProperty[];
107-
blockProperties: ComponentDescriptorBlockProperty[];
108-
events: ComponentDescriptorEvent[];
109-
methods: ComponentDescriptorMethod[];
110-
}
111-
11293
// ===== Component Metadata Types =====
11394
export interface ComponentMetadata {
11495
validateComponents(components: Component[]): { valid: boolean; errors: string[] }
@@ -154,7 +135,7 @@ export interface ComponentDescriptorProperty {
154135
name: string;
155136
editorType: string;
156137
defaultValue: string;
157-
propertyType: string;
138+
propertyType?: string;
158139
editorArgs: any[];
159140
}
160141

@@ -163,20 +144,20 @@ export interface ComponentDescriptorBlockProperty {
163144
description: string;
164145
type: string;
165146
rw: string;
166-
deprecated: string;
147+
deprecated: boolean;
167148
}
168149

169150
export interface ComponentDescriptorEvent {
170151
name: string;
171152
description: string;
172-
deprecated: string;
153+
deprecated: boolean;
173154
params: ComponentDescriptorParam[];
174155
}
175156

176157
export interface ComponentDescriptorMethod {
177158
name: string;
178159
description: string;
179-
deprecated: string;
160+
deprecated: boolean;
180161
params: ComponentDescriptorParam[];
181162
returnType?: string;
182163
}
@@ -189,14 +170,14 @@ export interface ComponentDescriptorParam {
189170
export interface ExtensionDescriptorJson {
190171
type: string;
191172
name: string;
192-
external: "true" | "false";
173+
external: boolean;
193174
version: string;
194175
dateBuilt: string;
195176
categoryString: string;
196177
helpString: string;
197178
helpUrl: string;
198-
showOnPalette: "true" | "false";
199-
nonVisible: "true" | "false";
179+
showOnPalette: boolean;
180+
nonVisible: boolean;
200181
iconName: string;
201182
androidMinSdk: number;
202183
versionName: string;

src/utils/property-processor.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { ComponentProperty } from "../component.js";
2-
import type { ComponentDescriptorJson, ComponentJson } from "../types.js";
2+
import type { ComponentJson } from "../types.js";
3+
import type { ComponentDescriptor } from "../validators/component-descriptor.js";
34

45
export function resolveProperties(
5-
componentDescriptor: ComponentDescriptorJson,
6+
componentDescriptor: ComponentDescriptor,
67
propertyJSON: ComponentJson,
78
): ComponentProperty[] {
89
return componentDescriptor.properties.map(property => {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { z } from "zod";
2+
3+
/**
4+
* Zod schema for ComponentDescriptorParam
5+
*/
6+
export const ComponentDescriptorParamSchema = z.object({
7+
name: z.string(),
8+
type: z.string(),
9+
});
10+
11+
/**
12+
* Zod schema for ComponentDescriptorProperty
13+
*/
14+
export const ComponentDescriptorPropertySchema = z.object({
15+
name: z.string(),
16+
editorType: z.string(),
17+
defaultValue: z.string(),
18+
/**
19+
* Kodular Creator specific extension - not present in MIT App Inventor.
20+
* Typically has values like "common", "advanced", etc.
21+
*/
22+
propertyType: z.string().optional(),
23+
editorArgs: z.array(z.any()),
24+
});
25+
26+
/**
27+
* Zod schema for ComponentDescriptorBlockProperty
28+
*/
29+
export const ComponentDescriptorBlockPropertySchema = z.object({
30+
name: z.string(),
31+
description: z.string(),
32+
type: z.string(),
33+
rw: z.string(),
34+
deprecated: z.stringbool(),
35+
});
36+
37+
/**
38+
* Zod schema for ComponentDescriptorEvent
39+
*/
40+
export const ComponentDescriptorEventSchema = z.object({
41+
name: z.string(),
42+
description: z.string(),
43+
deprecated: z.stringbool(),
44+
params: z.array(ComponentDescriptorParamSchema),
45+
});
46+
47+
/**
48+
* Zod schema for ComponentDescriptorMethod
49+
*/
50+
export const ComponentDescriptorMethodSchema = z.object({
51+
name: z.string(),
52+
description: z.string(),
53+
deprecated: z.stringbool(),
54+
params: z.array(ComponentDescriptorParamSchema),
55+
returnType: z.string().optional(),
56+
});
57+
58+
/**
59+
* Zod schema for ComponentDescriptor
60+
*/
61+
export const ComponentDescriptorSchema = z.object({
62+
type: z.string(),
63+
name: z.string(),
64+
external: z.stringbool(),
65+
version: z.string(),
66+
dateBuilt: z.string(),
67+
categoryString: z.string(),
68+
helpString: z.string(),
69+
helpUrl: z.string(),
70+
showOnPalette: z.stringbool(),
71+
nonVisible: z.stringbool(),
72+
iconName: z.string(),
73+
androidMinSdk: z.union([z.string(), z.number()]),
74+
properties: z.array(ComponentDescriptorPropertySchema),
75+
blockProperties: z.array(ComponentDescriptorBlockPropertySchema),
76+
events: z.array(ComponentDescriptorEventSchema),
77+
methods: z.array(ComponentDescriptorMethodSchema),
78+
});
79+
80+
export const ComponentDescriptorsSchema = z.array(ComponentDescriptorSchema);
81+
82+
/**
83+
* Type inferred from the Zod schema for ComponentDescriptor
84+
*/
85+
export type ComponentDescriptor = z.infer<typeof ComponentDescriptorSchema>;
86+
87+
/**
88+
* Type inferred from the Zod schema for ComponentDescriptors
89+
*/
90+
export type ComponentDescriptors = z.infer<typeof ComponentDescriptorsSchema>;

0 commit comments

Comments
 (0)