@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.
- package/INTEGRATION.md +354 -0
- package/LICENSE +373 -0
- package/README.md +101 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -0
- package/dist/express-parser.d.ts +75 -0
- package/dist/express-parser.d.ts.map +1 -0
- package/dist/express-parser.js +318 -0
- package/dist/express-parser.js.map +1 -0
- package/dist/generator.d.ts +10 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +64 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/typescript-generator.d.ts +28 -0
- package/dist/typescript-generator.d.ts.map +1 -0
- package/dist/typescript-generator.js +400 -0
- package/dist/typescript-generator.js.map +1 -0
- package/generated/ifc4/entities.ts +6978 -0
- package/generated/ifc4/enums.ts +2471 -0
- package/generated/ifc4/index.ts +15 -0
- package/generated/ifc4/schema-registry.ts +60726 -0
- package/generated/ifc4/selects.ts +191 -0
- package/generated/ifc4/test-compile.ts +37 -0
- package/generated/ifc4/types.ts +3104 -0
- package/generated/ifc4x3/entities.ts +7836 -0
- package/generated/ifc4x3/enums.ts +3100 -0
- package/generated/ifc4x3/index.ts +15 -0
- package/generated/ifc4x3/schema-registry.ts +71023 -0
- package/generated/ifc4x3/selects.ts +194 -0
- package/generated/ifc4x3/types.ts +3707 -0
- package/package.json +32 -0
- package/schemas/IFC4X3.exp +13984 -0
- package/schemas/IFC4_ADD2_TC1.exp +12399 -0
- package/src/cli.ts +41 -0
- package/src/express-parser.ts +441 -0
- package/src/generator.ts +81 -0
- package/src/index.ts +31 -0
- package/src/typescript-generator.ts +496 -0
- package/test/express-parser.test.ts +363 -0
- package/test/integration.test.ts +246 -0
- package/test/typescript-generator.test.ts +348 -0
- package/tsconfig.json +20 -0
- 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
|
+
});
|