@ifc-lite/codegen 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/INTEGRATION.md +354 -0
  2. package/LICENSE +373 -0
  3. package/README.md +101 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +35 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/express-parser.d.ts +75 -0
  9. package/dist/express-parser.d.ts.map +1 -0
  10. package/dist/express-parser.js +318 -0
  11. package/dist/express-parser.js.map +1 -0
  12. package/dist/generator.d.ts +10 -0
  13. package/dist/generator.d.ts.map +1 -0
  14. package/dist/generator.js +64 -0
  15. package/dist/generator.js.map +1 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +12 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/typescript-generator.d.ts +28 -0
  21. package/dist/typescript-generator.d.ts.map +1 -0
  22. package/dist/typescript-generator.js +400 -0
  23. package/dist/typescript-generator.js.map +1 -0
  24. package/generated/ifc4/entities.ts +6978 -0
  25. package/generated/ifc4/enums.ts +2471 -0
  26. package/generated/ifc4/index.ts +15 -0
  27. package/generated/ifc4/schema-registry.ts +60726 -0
  28. package/generated/ifc4/selects.ts +191 -0
  29. package/generated/ifc4/test-compile.ts +37 -0
  30. package/generated/ifc4/types.ts +3104 -0
  31. package/generated/ifc4x3/entities.ts +7836 -0
  32. package/generated/ifc4x3/enums.ts +3100 -0
  33. package/generated/ifc4x3/index.ts +15 -0
  34. package/generated/ifc4x3/schema-registry.ts +71023 -0
  35. package/generated/ifc4x3/selects.ts +194 -0
  36. package/generated/ifc4x3/types.ts +3707 -0
  37. package/package.json +32 -0
  38. package/schemas/IFC4X3.exp +13984 -0
  39. package/schemas/IFC4_ADD2_TC1.exp +12399 -0
  40. package/src/cli.ts +41 -0
  41. package/src/express-parser.ts +441 -0
  42. package/src/generator.ts +81 -0
  43. package/src/index.ts +31 -0
  44. package/src/typescript-generator.ts +496 -0
  45. package/test/express-parser.test.ts +363 -0
  46. package/test/integration.test.ts +246 -0
  47. package/test/typescript-generator.test.ts +348 -0
  48. package/tsconfig.json +20 -0
  49. package/vitest.config.ts +18 -0
@@ -0,0 +1,363 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Tests for EXPRESS parser
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import { parseExpressSchema, getAllAttributes, getInheritanceChain } from '../src/express-parser.js';
11
+
12
+ describe('EXPRESS Parser', () => {
13
+ describe('Schema parsing', () => {
14
+ it('should parse schema name', () => {
15
+ const schema = parseExpressSchema(`
16
+ SCHEMA IFC4_ADD2_TC1;
17
+ END_SCHEMA;
18
+ `);
19
+
20
+ expect(schema.name).toBe('IFC4_ADD2_TC1');
21
+ });
22
+
23
+ it('should handle comments', () => {
24
+ const schema = parseExpressSchema(`
25
+ (* This is a comment *)
26
+ SCHEMA TEST;
27
+ (* Multi-line
28
+ comment
29
+ *)
30
+ END_SCHEMA;
31
+ `);
32
+
33
+ expect(schema.name).toBe('TEST');
34
+ });
35
+ });
36
+
37
+ describe('Entity parsing', () => {
38
+ it('should parse simple entity', () => {
39
+ const schema = parseExpressSchema(`
40
+ SCHEMA TEST;
41
+
42
+ ENTITY IfcRoot;
43
+ GlobalId : IfcGloballyUniqueId;
44
+ Name : OPTIONAL IfcLabel;
45
+ END_ENTITY;
46
+
47
+ END_SCHEMA;
48
+ `);
49
+
50
+ expect(schema.entities).toHaveLength(1);
51
+ expect(schema.entities[0].name).toBe('IfcRoot');
52
+ expect(schema.entities[0].attributes).toHaveLength(2);
53
+
54
+ const globalId = schema.entities[0].attributes[0];
55
+ expect(globalId.name).toBe('GlobalId');
56
+ expect(globalId.type).toBe('IfcGloballyUniqueId');
57
+ expect(globalId.optional).toBe(false);
58
+
59
+ const name = schema.entities[0].attributes[1];
60
+ expect(name.name).toBe('Name');
61
+ expect(name.type).toBe('IfcLabel');
62
+ expect(name.optional).toBe(true);
63
+ });
64
+
65
+ it('should parse entity with inheritance', () => {
66
+ const schema = parseExpressSchema(`
67
+ SCHEMA TEST;
68
+
69
+ ENTITY IfcRoot;
70
+ GlobalId : IfcGloballyUniqueId;
71
+ END_ENTITY;
72
+
73
+ ENTITY IfcObject
74
+ SUBTYPE OF (IfcRoot);
75
+ ObjectType : OPTIONAL IfcLabel;
76
+ END_ENTITY;
77
+
78
+ END_SCHEMA;
79
+ `);
80
+
81
+ expect(schema.entities).toHaveLength(2);
82
+
83
+ const ifcObject = schema.entities.find(e => e.name === 'IfcObject');
84
+ expect(ifcObject).toBeDefined();
85
+ expect(ifcObject?.supertype).toBe('IfcRoot');
86
+ });
87
+
88
+ it('should parse abstract entity', () => {
89
+ const schema = parseExpressSchema(`
90
+ SCHEMA TEST;
91
+
92
+ ENTITY IfcRoot
93
+ ABSTRACT SUPERTYPE OF (ONEOF
94
+ (IfcObject));
95
+ GlobalId : IfcGloballyUniqueId;
96
+ END_ENTITY;
97
+
98
+ END_SCHEMA;
99
+ `);
100
+
101
+ const root = schema.entities[0];
102
+ expect(root.isAbstract).toBe(true);
103
+ expect(root.supertypeOf).toEqual(['IfcObject']);
104
+ });
105
+
106
+ it('should parse LIST attributes', () => {
107
+ const schema = parseExpressSchema(`
108
+ SCHEMA TEST;
109
+
110
+ ENTITY IfcTest;
111
+ Items : LIST [1:?] OF IfcCartesianPoint;
112
+ END_ENTITY;
113
+
114
+ END_SCHEMA;
115
+ `);
116
+
117
+ const attr = schema.entities[0].attributes[0];
118
+ expect(attr.name).toBe('Items');
119
+ expect(attr.type).toBe('IfcCartesianPoint');
120
+ expect(attr.isList).toBe(true);
121
+ expect(attr.arrayBounds).toEqual([1, Infinity]); // ? becomes Infinity
122
+ });
123
+
124
+ it('should parse ARRAY attributes', () => {
125
+ const schema = parseExpressSchema(`
126
+ SCHEMA TEST;
127
+
128
+ ENTITY IfcTest;
129
+ Coords : ARRAY [1:3] OF REAL;
130
+ END_ENTITY;
131
+
132
+ END_SCHEMA;
133
+ `);
134
+
135
+ const attr = schema.entities[0].attributes[0];
136
+ expect(attr.name).toBe('Coords');
137
+ expect(attr.type).toBe('REAL');
138
+ expect(attr.isArray).toBe(true);
139
+ expect(attr.arrayBounds).toEqual([1, 3]);
140
+ });
141
+
142
+ it('should parse SET attributes', () => {
143
+ const schema = parseExpressSchema(`
144
+ SCHEMA TEST;
145
+
146
+ ENTITY IfcTest;
147
+ Tags : SET [1:?] OF IfcLabel;
148
+ END_ENTITY;
149
+
150
+ END_SCHEMA;
151
+ `);
152
+
153
+ const attr = schema.entities[0].attributes[0];
154
+ expect(attr.name).toBe('Tags');
155
+ expect(attr.type).toBe('IfcLabel');
156
+ expect(attr.isSet).toBe(true);
157
+ });
158
+ });
159
+
160
+ describe('Type parsing', () => {
161
+ it('should parse simple types', () => {
162
+ const schema = parseExpressSchema(`
163
+ SCHEMA TEST;
164
+
165
+ TYPE IfcLabel = STRING;
166
+ END_TYPE;
167
+
168
+ TYPE IfcLengthMeasure = REAL;
169
+ END_TYPE;
170
+
171
+ END_SCHEMA;
172
+ `);
173
+
174
+ expect(schema.types).toHaveLength(2);
175
+
176
+ const label = schema.types.find(t => t.name === 'IfcLabel');
177
+ expect(label?.underlyingType).toBe('STRING');
178
+
179
+ const length = schema.types.find(t => t.name === 'IfcLengthMeasure');
180
+ expect(length?.underlyingType).toBe('REAL');
181
+ });
182
+
183
+ it('should parse types with WHERE clauses', () => {
184
+ const schema = parseExpressSchema(`
185
+ SCHEMA TEST;
186
+
187
+ TYPE IfcPositiveInteger = INTEGER;
188
+ WHERE
189
+ WR1 : SELF > 0;
190
+ END_TYPE;
191
+
192
+ END_SCHEMA;
193
+ `);
194
+
195
+ const type = schema.types[0];
196
+ expect(type.whereRules).toBeDefined();
197
+ expect(type.whereRules).toHaveLength(1);
198
+ });
199
+ });
200
+
201
+ describe('Enum parsing', () => {
202
+ it('should parse enumerations', () => {
203
+ const schema = parseExpressSchema(`
204
+ SCHEMA TEST;
205
+
206
+ TYPE IfcWallTypeEnum = ENUMERATION OF
207
+ (MOVABLE
208
+ ,PARAPET
209
+ ,PARTITIONING
210
+ ,SOLIDWALL);
211
+ END_TYPE;
212
+
213
+ END_SCHEMA;
214
+ `);
215
+
216
+ expect(schema.enums).toHaveLength(1);
217
+
218
+ const enumDef = schema.enums[0];
219
+ expect(enumDef.name).toBe('IfcWallTypeEnum');
220
+ expect(enumDef.values).toEqual([
221
+ 'MOVABLE',
222
+ 'PARAPET',
223
+ 'PARTITIONING',
224
+ 'SOLIDWALL',
225
+ ]);
226
+ });
227
+
228
+ it('should handle multi-line enums', () => {
229
+ const schema = parseExpressSchema(`
230
+ SCHEMA TEST;
231
+
232
+ TYPE IfcColorEnum = ENUMERATION OF
233
+ (RED,
234
+ GREEN,
235
+ BLUE);
236
+ END_TYPE;
237
+
238
+ END_SCHEMA;
239
+ `);
240
+
241
+ const enumDef = schema.enums[0];
242
+ expect(enumDef.values).toEqual(['RED', 'GREEN', 'BLUE']);
243
+ });
244
+ });
245
+
246
+ describe('Select parsing', () => {
247
+ it('should parse SELECT types', () => {
248
+ const schema = parseExpressSchema(`
249
+ SCHEMA TEST;
250
+
251
+ TYPE IfcValue = SELECT
252
+ (IfcLabel
253
+ ,IfcInteger
254
+ ,IfcReal);
255
+ END_TYPE;
256
+
257
+ END_SCHEMA;
258
+ `);
259
+
260
+ expect(schema.selects).toHaveLength(1);
261
+
262
+ const select = schema.selects[0];
263
+ expect(select.name).toBe('IfcValue');
264
+ expect(select.types).toEqual(['IfcLabel', 'IfcInteger', 'IfcReal']);
265
+ });
266
+ });
267
+
268
+ describe('Inheritance utilities', () => {
269
+ it('should get all attributes including inherited', () => {
270
+ const schema = parseExpressSchema(`
271
+ SCHEMA TEST;
272
+
273
+ ENTITY IfcRoot;
274
+ GlobalId : IfcGloballyUniqueId;
275
+ Name : OPTIONAL IfcLabel;
276
+ END_ENTITY;
277
+
278
+ ENTITY IfcObject
279
+ SUBTYPE OF (IfcRoot);
280
+ ObjectType : OPTIONAL IfcLabel;
281
+ END_ENTITY;
282
+
283
+ ENTITY IfcProduct
284
+ SUBTYPE OF (IfcObject);
285
+ Tag : OPTIONAL IfcLabel;
286
+ END_ENTITY;
287
+
288
+ END_SCHEMA;
289
+ `);
290
+
291
+ const product = schema.entities.find(e => e.name === 'IfcProduct')!;
292
+ const allAttrs = getAllAttributes(product, schema);
293
+
294
+ expect(allAttrs).toHaveLength(4);
295
+ expect(allAttrs.map(a => a.name)).toEqual([
296
+ 'Tag', // IfcProduct
297
+ 'ObjectType', // IfcObject
298
+ 'GlobalId', // IfcRoot
299
+ 'Name', // IfcRoot
300
+ ]);
301
+ });
302
+
303
+ it('should get inheritance chain', () => {
304
+ const schema = parseExpressSchema(`
305
+ SCHEMA TEST;
306
+
307
+ ENTITY IfcRoot;
308
+ GlobalId : IfcGloballyUniqueId;
309
+ END_ENTITY;
310
+
311
+ ENTITY IfcObject
312
+ SUBTYPE OF (IfcRoot);
313
+ ObjectType : OPTIONAL IfcLabel;
314
+ END_ENTITY;
315
+
316
+ ENTITY IfcProduct
317
+ SUBTYPE OF (IfcObject);
318
+ Tag : OPTIONAL IfcLabel;
319
+ END_ENTITY;
320
+
321
+ END_SCHEMA;
322
+ `);
323
+
324
+ const product = schema.entities.find(e => e.name === 'IfcProduct')!;
325
+ const chain = getInheritanceChain(product, schema);
326
+
327
+ expect(chain).toEqual(['IfcRoot', 'IfcObject', 'IfcProduct']);
328
+ });
329
+ });
330
+
331
+ describe('Real IFC schema parsing', () => {
332
+ it('should parse IfcWall from real schema', () => {
333
+ const schema = parseExpressSchema(`
334
+ SCHEMA IFC4;
335
+
336
+ ENTITY IfcBuildingElement
337
+ SUBTYPE OF (IfcElement);
338
+ END_ENTITY;
339
+
340
+ ENTITY IfcWall
341
+ SUPERTYPE OF (ONEOF
342
+ (IfcWallElementedCase
343
+ ,IfcWallStandardCase))
344
+ SUBTYPE OF (IfcBuildingElement);
345
+ PredefinedType : OPTIONAL IfcWallTypeEnum;
346
+ WHERE
347
+ CorrectPredefinedType : NOT(EXISTS(PredefinedType)) OR
348
+ (PredefinedType <> IfcWallTypeEnum.USERDEFINED);
349
+ END_ENTITY;
350
+
351
+ END_SCHEMA;
352
+ `);
353
+
354
+ const wall = schema.entities.find(e => e.name === 'IfcWall')!;
355
+ expect(wall).toBeDefined();
356
+ expect(wall.supertype).toBe('IfcBuildingElement');
357
+ expect(wall.supertypeOf).toContain('IfcWallStandardCase');
358
+ expect(wall.attributes).toHaveLength(1);
359
+ expect(wall.attributes[0].name).toBe('PredefinedType');
360
+ expect(wall.whereRules).toBeDefined();
361
+ });
362
+ });
363
+ });
@@ -0,0 +1,246 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Integration tests - full code generation from real schemas
7
+ */
8
+
9
+ import { describe, it, expect } from 'vitest';
10
+ import { readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { parseExpressSchema } from '../src/express-parser.js';
13
+ import { generateTypeScript } from '../src/typescript-generator.js';
14
+
15
+ describe('Integration Tests', () => {
16
+ describe('IFC4 ADD2 TC1 Schema', () => {
17
+ it('should parse IFC4 schema without errors', () => {
18
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
19
+ const content = readFileSync(schemaPath, 'utf-8');
20
+
21
+ expect(() => {
22
+ parseExpressSchema(content);
23
+ }).not.toThrow();
24
+ });
25
+
26
+ it('should find expected entities in IFC4', () => {
27
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
28
+ const content = readFileSync(schemaPath, 'utf-8');
29
+ const schema = parseExpressSchema(content);
30
+
31
+ // Check for key entities
32
+ const entityNames = schema.entities.map(e => e.name);
33
+
34
+ expect(entityNames).toContain('IfcRoot');
35
+ expect(entityNames).toContain('IfcProject');
36
+ expect(entityNames).toContain('IfcBuilding');
37
+ expect(entityNames).toContain('IfcWall');
38
+ expect(entityNames).toContain('IfcDoor');
39
+ expect(entityNames).toContain('IfcWindow');
40
+ expect(entityNames).toContain('IfcSlab');
41
+ expect(entityNames).toContain('IfcColumn');
42
+ expect(entityNames).toContain('IfcBeam');
43
+
44
+ console.log(`\n✓ Parsed ${schema.entities.length} entities from IFC4`);
45
+ });
46
+
47
+ it('should find expected types in IFC4', () => {
48
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
49
+ const content = readFileSync(schemaPath, 'utf-8');
50
+ const schema = parseExpressSchema(content);
51
+
52
+ // Check for key types
53
+ const typeNames = schema.types.map(t => t.name);
54
+
55
+ expect(typeNames).toContain('IfcLabel');
56
+ expect(typeNames).toContain('IfcText');
57
+ expect(typeNames).toContain('IfcLengthMeasure');
58
+ expect(typeNames).toContain('IfcAreaMeasure');
59
+ expect(typeNames).toContain('IfcVolumeMeasure');
60
+
61
+ console.log(`✓ Parsed ${schema.types.length} types from IFC4`);
62
+ });
63
+
64
+ it('should find expected enums in IFC4', () => {
65
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
66
+ const content = readFileSync(schemaPath, 'utf-8');
67
+ const schema = parseExpressSchema(content);
68
+
69
+ // Check for key enums
70
+ const enumNames = schema.enums.map(e => e.name);
71
+
72
+ expect(enumNames).toContain('IfcWallTypeEnum');
73
+ expect(enumNames).toContain('IfcDoorTypeEnum');
74
+ expect(enumNames).toContain('IfcWindowTypeEnum');
75
+
76
+ console.log(`✓ Parsed ${schema.enums.length} enums from IFC4`);
77
+ });
78
+
79
+ it('should find expected selects in IFC4', () => {
80
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
81
+ const content = readFileSync(schemaPath, 'utf-8');
82
+ const schema = parseExpressSchema(content);
83
+
84
+ // Check for key selects
85
+ const selectNames = schema.selects.map(s => s.name);
86
+
87
+ expect(selectNames).toContain('IfcValue');
88
+
89
+ console.log(`✓ Parsed ${schema.selects.length} select types from IFC4`);
90
+ });
91
+
92
+ it('should correctly parse IfcWall with all attributes', () => {
93
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
94
+ const content = readFileSync(schemaPath, 'utf-8');
95
+ const schema = parseExpressSchema(content);
96
+
97
+ const wall = schema.entities.find(e => e.name === 'IfcWall');
98
+
99
+ expect(wall).toBeDefined();
100
+ expect(wall?.supertype).toBe('IfcBuildingElement');
101
+ expect(wall?.attributes).toHaveLength(1);
102
+ expect(wall?.attributes[0].name).toBe('PredefinedType');
103
+ expect(wall?.attributes[0].optional).toBe(true);
104
+
105
+ console.log('\n✓ IfcWall parsed correctly');
106
+ console.log(` - Supertype: ${wall?.supertype}`);
107
+ console.log(` - Attributes: ${wall?.attributes.length}`);
108
+ });
109
+
110
+ it('should correctly parse IfcRoot hierarchy', () => {
111
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
112
+ const content = readFileSync(schemaPath, 'utf-8');
113
+ const schema = parseExpressSchema(content);
114
+
115
+ const root = schema.entities.find(e => e.name === 'IfcRoot');
116
+
117
+ expect(root).toBeDefined();
118
+ expect(root?.isAbstract).toBe(true);
119
+ expect(root?.attributes.length).toBeGreaterThan(0);
120
+
121
+ const globalId = root?.attributes.find(a => a.name === 'GlobalId');
122
+ expect(globalId).toBeDefined();
123
+ expect(globalId?.optional).toBe(false);
124
+
125
+ console.log('\n✓ IfcRoot parsed correctly');
126
+ console.log(` - Abstract: ${root?.isAbstract}`);
127
+ console.log(` - Attributes: ${root?.attributes.map(a => a.name).join(', ')}`);
128
+ });
129
+
130
+ it('should generate valid TypeScript from IFC4', () => {
131
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
132
+ const content = readFileSync(schemaPath, 'utf-8');
133
+ const schema = parseExpressSchema(content);
134
+
135
+ expect(() => {
136
+ generateTypeScript(schema);
137
+ }).not.toThrow();
138
+ });
139
+
140
+ it('should generate compilable TypeScript interfaces', () => {
141
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
142
+ const content = readFileSync(schemaPath, 'utf-8');
143
+ const schema = parseExpressSchema(content);
144
+ const code = generateTypeScript(schema);
145
+
146
+ // Check for valid TypeScript syntax patterns
147
+ expect(code.entities).toContain('export interface');
148
+ expect(code.entities).toContain('extends');
149
+ expect(code.entities).not.toContain('undefined');
150
+ // Note: 'null' may appear in LOGICAL types (boolean | null) which is valid
151
+
152
+ // Check for proper interface structure
153
+ expect(code.entities).toMatch(/export interface \w+ \{/);
154
+ expect(code.entities).toMatch(/\w+\??: \w+;/);
155
+
156
+ console.log('\n✓ Generated TypeScript is well-formed');
157
+ });
158
+
159
+ it('should generate complete schema registry', () => {
160
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
161
+ const content = readFileSync(schemaPath, 'utf-8');
162
+ const schema = parseExpressSchema(content);
163
+ const code = generateTypeScript(schema);
164
+
165
+ expect(code.schemaRegistry).toContain('export const SCHEMA_REGISTRY');
166
+ expect(code.schemaRegistry).toContain('entities: {');
167
+ expect(code.schemaRegistry).toContain('types: {');
168
+ expect(code.schemaRegistry).toContain('enums: {');
169
+ expect(code.schemaRegistry).toContain('selects: {');
170
+
171
+ // Check for helper functions
172
+ expect(code.schemaRegistry).toContain('export function getEntityMetadata');
173
+ expect(code.schemaRegistry).toContain('export function isKnownEntity');
174
+
175
+ console.log('✓ Schema registry includes all sections');
176
+ });
177
+ });
178
+
179
+ describe('IFC4X3 Schema', () => {
180
+ it('should parse IFC4X3 schema without errors', () => {
181
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4X3.exp');
182
+ const content = readFileSync(schemaPath, 'utf-8');
183
+
184
+ expect(() => {
185
+ parseExpressSchema(content);
186
+ }).not.toThrow();
187
+ });
188
+
189
+ it('should find infrastructure entities in IFC4X3', () => {
190
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4X3.exp');
191
+ const content = readFileSync(schemaPath, 'utf-8');
192
+ const schema = parseExpressSchema(content);
193
+
194
+ const entityNames = schema.entities.map(e => e.name);
195
+
196
+ // Check for infrastructure entities (new in IFC4X3)
197
+ const hasRoad = entityNames.some(n => n.includes('Road'));
198
+ const hasBridge = entityNames.some(n => n.includes('Bridge'));
199
+ const hasRailway = entityNames.some(n => n.includes('Railway'));
200
+
201
+ console.log(`\n✓ Parsed ${schema.entities.length} entities from IFC4X3`);
202
+ console.log(` - Road entities: ${hasRoad}`);
203
+ console.log(` - Bridge entities: ${hasBridge}`);
204
+ console.log(` - Railway entities: ${hasRailway}`);
205
+ });
206
+
207
+ it('should generate valid TypeScript from IFC4X3', () => {
208
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4X3.exp');
209
+ const content = readFileSync(schemaPath, 'utf-8');
210
+ const schema = parseExpressSchema(content);
211
+
212
+ expect(() => {
213
+ generateTypeScript(schema);
214
+ }).not.toThrow();
215
+
216
+ console.log('✓ Successfully generated TypeScript from IFC4X3');
217
+ });
218
+ });
219
+
220
+ describe('Generated code statistics', () => {
221
+ it('should report schema coverage statistics', () => {
222
+ const schemaPath = join(process.cwd(), 'schemas', 'IFC4_ADD2_TC1.exp');
223
+ const content = readFileSync(schemaPath, 'utf-8');
224
+ const schema = parseExpressSchema(content);
225
+
226
+ console.log('\n📊 IFC4 ADD2 TC1 Schema Statistics:');
227
+ console.log(` - Entities: ${schema.entities.length}`);
228
+ console.log(` - Types: ${schema.types.length}`);
229
+ console.log(` - Enums: ${schema.enums.length}`);
230
+ console.log(` - Selects: ${schema.selects.length}`);
231
+ console.log(` - Total: ${schema.entities.length + schema.types.length + schema.enums.length + schema.selects.length}`);
232
+
233
+ // Abstract entities
234
+ const abstractCount = schema.entities.filter(e => e.isAbstract).length;
235
+ console.log(` - Abstract entities: ${abstractCount}`);
236
+
237
+ // Top-level entities (no parent)
238
+ const topLevel = schema.entities.filter(e => !e.supertype).length;
239
+ console.log(` - Top-level entities: ${topLevel}`);
240
+
241
+ // Entities with the most attributes
242
+ const sorted = [...schema.entities].sort((a, b) => b.attributes.length - a.attributes.length);
243
+ console.log(` - Entity with most attributes: ${sorted[0].name} (${sorted[0].attributes.length})`);
244
+ });
245
+ });
246
+ });