@memoarchitect/tools 0.4.4 → 0.4.6
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/README.md +17 -9
- package/package.json +25 -24
- package/packages/tools/lib/bin/memo.js +0 -0
- package/packages/tools/lib/commands/dev.d.ts.map +1 -1
- package/packages/tools/lib/commands/dev.js +6 -1
- package/packages/tools/lib/commands/dev.js.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.d.ts +1 -1
- package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.js +20 -16
- package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.d.ts +1380 -115
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +1468 -843
- package/packages/tools/lib/language/generated/ast.js.map +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/grammar.js +97 -329
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/language/generated/module.d.ts +1 -1
- package/packages/tools/lib/language/generated/module.js +1 -1
- package/packages/tools/lib/model/ontology-loader.js +1 -1
- package/packages/tools/lib/model/ontology-loader.js.map +1 -1
- package/packages/tools/lib/model/paths.d.ts +6 -4
- package/packages/tools/lib/model/paths.d.ts.map +1 -1
- package/packages/tools/lib/model/paths.js +20 -9
- package/packages/tools/lib/model/paths.js.map +1 -1
- package/packages/tools/lib/__tests__/archetype-parsing.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/archetype-parsing.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/archetype-parsing.test.js +0 -42
- package/packages/tools/lib/__tests__/archetype-parsing.test.js.map +0 -1
- package/packages/tools/lib/__tests__/architecture-discovery.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/architecture-discovery.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/architecture-discovery.test.js +0 -82
- package/packages/tools/lib/__tests__/architecture-discovery.test.js.map +0 -1
- package/packages/tools/lib/__tests__/behavior.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/behavior.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/behavior.test.js +0 -766
- package/packages/tools/lib/__tests__/behavior.test.js.map +0 -1
- package/packages/tools/lib/__tests__/builder.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/builder.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/builder.test.js +0 -728
- package/packages/tools/lib/__tests__/builder.test.js.map +0 -1
- package/packages/tools/lib/__tests__/config-loader.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/config-loader.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/config-loader.test.js +0 -196
- package/packages/tools/lib/__tests__/config-loader.test.js.map +0 -1
- package/packages/tools/lib/__tests__/conformance.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/conformance.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/conformance.test.js +0 -467
- package/packages/tools/lib/__tests__/conformance.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-eval.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-eval.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-eval.test.js +0 -195
- package/packages/tools/lib/__tests__/constraint-eval.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-grammar.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-grammar.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-grammar.test.js +0 -163
- package/packages/tools/lib/__tests__/constraint-grammar.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-loader.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-loader.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-loader.test.js +0 -150
- package/packages/tools/lib/__tests__/constraint-loader.test.js.map +0 -1
- package/packages/tools/lib/__tests__/csv-io.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/csv-io.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/csv-io.test.js +0 -317
- package/packages/tools/lib/__tests__/csv-io.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf-doc-store.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf-doc-store.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf-doc-store.test.js +0 -94
- package/packages/tools/lib/__tests__/dhf-doc-store.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.js +0 -39
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf.test.js +0 -393
- package/packages/tools/lib/__tests__/dhf.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dimension-filter.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dimension-filter.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dimension-filter.test.js +0 -85
- package/packages/tools/lib/__tests__/dimension-filter.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dsm.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dsm.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dsm.test.js +0 -112
- package/packages/tools/lib/__tests__/dsm.test.js.map +0 -1
- package/packages/tools/lib/__tests__/e2e-workflow.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/e2e-workflow.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/e2e-workflow.test.js +0 -707
- package/packages/tools/lib/__tests__/e2e-workflow.test.js.map +0 -1
- package/packages/tools/lib/__tests__/file-watcher.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/file-watcher.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/file-watcher.test.js +0 -31
- package/packages/tools/lib/__tests__/file-watcher.test.js.map +0 -1
- package/packages/tools/lib/__tests__/impact.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/impact.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/impact.test.js +0 -82
- package/packages/tools/lib/__tests__/impact.test.js.map +0 -1
- package/packages/tools/lib/__tests__/importer.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/importer.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/importer.test.js +0 -336
- package/packages/tools/lib/__tests__/importer.test.js.map +0 -1
- package/packages/tools/lib/__tests__/kind-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/kind-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/kind-registry.test.js +0 -132
- package/packages/tools/lib/__tests__/kind-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/llm.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/llm.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/llm.test.js +0 -275
- package/packages/tools/lib/__tests__/llm.test.js.map +0 -1
- package/packages/tools/lib/__tests__/manifest-resolver.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/manifest-resolver.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/manifest-resolver.test.js +0 -97
- package/packages/tools/lib/__tests__/manifest-resolver.test.js.map +0 -1
- package/packages/tools/lib/__tests__/markdown-to-ir.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/markdown-to-ir.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/markdown-to-ir.test.js +0 -74
- package/packages/tools/lib/__tests__/markdown-to-ir.test.js.map +0 -1
- package/packages/tools/lib/__tests__/methodology-scope.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/methodology-scope.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/methodology-scope.test.js +0 -59
- package/packages/tools/lib/__tests__/methodology-scope.test.js.map +0 -1
- package/packages/tools/lib/__tests__/parser.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/parser.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/parser.test.js +0 -938
- package/packages/tools/lib/__tests__/parser.test.js.map +0 -1
- package/packages/tools/lib/__tests__/plugin.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/plugin.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/plugin.test.js +0 -308
- package/packages/tools/lib/__tests__/plugin.test.js.map +0 -1
- package/packages/tools/lib/__tests__/relationship-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/relationship-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/relationship-registry.test.js +0 -96
- package/packages/tools/lib/__tests__/relationship-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/rule-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/rule-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/rule-registry.test.js +0 -53
- package/packages/tools/lib/__tests__/rule-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/toolchain.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/toolchain.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/toolchain.test.js +0 -94
- package/packages/tools/lib/__tests__/toolchain.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-deriver-samples.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-deriver-samples.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-deriver-samples.test.js +0 -39
- package/packages/tools/lib/__tests__/view-deriver-samples.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-kinds.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-kinds.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-kinds.test.js +0 -263
- package/packages/tools/lib/__tests__/view-kinds.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-layout-store.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-layout-store.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-layout-store.test.js +0 -61
- package/packages/tools/lib/__tests__/view-layout-store.test.js.map +0 -1
|
@@ -1,938 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { EmptyFileSystem } from 'langium';
|
|
3
|
-
import { parseHelper } from 'langium/test';
|
|
4
|
-
import { createMemoSysMLServices } from '../language/memo-sysml-module.js';
|
|
5
|
-
const services = createMemoSysMLServices({ ...EmptyFileSystem }).MemoSysML;
|
|
6
|
-
const parse = parseHelper(services);
|
|
7
|
-
/** Helper: parse and assert no errors */
|
|
8
|
-
async function parseValid(input) {
|
|
9
|
-
const doc = await parse(input);
|
|
10
|
-
const errors = doc.parseResult.lexerErrors
|
|
11
|
-
.concat(doc.parseResult.parserErrors);
|
|
12
|
-
if (errors.length > 0) {
|
|
13
|
-
throw new Error(`Parse errors:\n${errors.map((e) => e.message).join('\n')}\n\nInput:\n${input}`);
|
|
14
|
-
}
|
|
15
|
-
return doc.parseResult.value;
|
|
16
|
-
}
|
|
17
|
-
async function parseErrors(input) {
|
|
18
|
-
const doc = await parse(input);
|
|
19
|
-
return doc.parseResult.lexerErrors
|
|
20
|
-
.concat(doc.parseResult.parserErrors)
|
|
21
|
-
.map((e) => e.message);
|
|
22
|
-
}
|
|
23
|
-
// ─── Basic constructs ────────────────────────────────────────────────────────
|
|
24
|
-
describe('Package', () => {
|
|
25
|
-
it('parses a simple package', async () => {
|
|
26
|
-
const model = await parseValid(`
|
|
27
|
-
package MyPackage {
|
|
28
|
-
}
|
|
29
|
-
`);
|
|
30
|
-
expect(model.members).toHaveLength(1);
|
|
31
|
-
const pkg = model.members[0];
|
|
32
|
-
expect(pkg.$type).toBe('PackageDeclaration');
|
|
33
|
-
expect(pkg.name).toBe('MyPackage');
|
|
34
|
-
});
|
|
35
|
-
it('parses nested packages', async () => {
|
|
36
|
-
const model = await parseValid(`
|
|
37
|
-
package Outer {
|
|
38
|
-
package Inner {
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
`);
|
|
42
|
-
const outer = model.members[0];
|
|
43
|
-
expect(outer.name).toBe('Outer');
|
|
44
|
-
const inner = outer.members[0];
|
|
45
|
-
expect(inner.$type).toBe('PackageDeclaration');
|
|
46
|
-
expect(inner.name).toBe('Inner');
|
|
47
|
-
});
|
|
48
|
-
// Strict SysML v2 subset: a package DECLARATION must use a single-identifier name.
|
|
49
|
-
it('rejects qualified package declarations (non-standard SysML v2)', async () => {
|
|
50
|
-
const errors = await parseErrors(`
|
|
51
|
-
package memo::arch::risk {
|
|
52
|
-
}
|
|
53
|
-
`);
|
|
54
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('SysML v2 metadata extension (metadata def + @application)', () => {
|
|
58
|
-
it('parses a metadata def', async () => {
|
|
59
|
-
const model = await parseValid(`
|
|
60
|
-
package Ext {
|
|
61
|
-
metadata def Provenance {
|
|
62
|
-
attribute source : String;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
`);
|
|
66
|
-
const pkg = model.members[0];
|
|
67
|
-
expect(pkg.members[0].$type).toBe('MetadataDefinition');
|
|
68
|
-
expect(pkg.members[0].name).toBe('Provenance');
|
|
69
|
-
});
|
|
70
|
-
it('parses metadata application (@Name) on a definition body and as a package member', async () => {
|
|
71
|
-
const model = await parseValid(`
|
|
72
|
-
package Ext {
|
|
73
|
-
metadata def Regulatory { attribute standard : String; }
|
|
74
|
-
@Regulatory;
|
|
75
|
-
part def Pump {
|
|
76
|
-
@Regulatory {
|
|
77
|
-
attribute redefines standard = "IEC 62304";
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
`);
|
|
82
|
-
const pkg = model.members[0];
|
|
83
|
-
const pkgApp = pkg.members.find(m => m.$type === 'MetadataApplication');
|
|
84
|
-
expect(pkgApp).toBeDefined();
|
|
85
|
-
expect(pkgApp.type).toBe('Regulatory');
|
|
86
|
-
const pump = pkg.members.find(m => m.$type === 'PartDefinition');
|
|
87
|
-
expect(pump.body.some((b) => b.$type === 'MetadataApplication')).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
it('rejects metadata applied with a qualified declaration name (still strict)', async () => {
|
|
90
|
-
const errors = await parseErrors(`
|
|
91
|
-
package memo::ext {
|
|
92
|
-
metadata def X;
|
|
93
|
-
}
|
|
94
|
-
`);
|
|
95
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
describe('Strict SysML v2 subset: non-standard forms are rejected', () => {
|
|
99
|
-
it('rejects bare shorthand redefinition (`name = value;`)', async () => {
|
|
100
|
-
const errors = await parseErrors(`
|
|
101
|
-
package Test {
|
|
102
|
-
part p : SomeType {
|
|
103
|
-
title = "x";
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
`);
|
|
107
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
108
|
-
});
|
|
109
|
-
it('accepts the standard redefinition form (`attribute redefines name = value;`)', async () => {
|
|
110
|
-
const model = await parseValid(`
|
|
111
|
-
package Test {
|
|
112
|
-
part p : SomeType {
|
|
113
|
-
attribute redefines title = "x";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
`);
|
|
117
|
-
expect(model.members).toHaveLength(1);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe('Import', () => {
|
|
121
|
-
it('parses wildcard import', async () => {
|
|
122
|
-
const model = await parseValid(`
|
|
123
|
-
package Test {
|
|
124
|
-
import MEMO_Ontology_Medical::*;
|
|
125
|
-
}
|
|
126
|
-
`);
|
|
127
|
-
const pkg = model.members[0];
|
|
128
|
-
const imp = pkg.members[0];
|
|
129
|
-
expect(imp.$type).toBe('ImportDeclaration');
|
|
130
|
-
if (imp.$type === 'ImportDeclaration') {
|
|
131
|
-
expect(imp.path).toBe('MEMO_Ontology_Medical::*');
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
it('parses qualified import', async () => {
|
|
135
|
-
const model = await parseValid(`
|
|
136
|
-
package Test {
|
|
137
|
-
import MEMO_Ontology_Core::Stakeholder;
|
|
138
|
-
}
|
|
139
|
-
`);
|
|
140
|
-
const pkg = model.members[0];
|
|
141
|
-
const imp = pkg.members[0];
|
|
142
|
-
if (imp.$type === 'ImportDeclaration') {
|
|
143
|
-
expect(imp.path).toBe('MEMO_Ontology_Core::Stakeholder');
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
// ─── Definitions ─────────────────────────────────────────────────────────────
|
|
148
|
-
describe('PartDefinition', () => {
|
|
149
|
-
it('parses part def with attributes', async () => {
|
|
150
|
-
const model = await parseValid(`
|
|
151
|
-
package Test {
|
|
152
|
-
part def Actor {
|
|
153
|
-
attribute name : String;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
`);
|
|
157
|
-
const pkg = model.members[0];
|
|
158
|
-
const partDef = pkg.members[0];
|
|
159
|
-
expect(partDef.$type).toBe('PartDefinition');
|
|
160
|
-
expect(partDef.name).toBe('Actor');
|
|
161
|
-
expect(partDef.body).toHaveLength(1);
|
|
162
|
-
const attr = partDef.body[0];
|
|
163
|
-
expect(attr.name).toBe('name');
|
|
164
|
-
expect(attr.type).toBe('String');
|
|
165
|
-
});
|
|
166
|
-
it('parses part def with specialization', async () => {
|
|
167
|
-
const model = await parseValid(`
|
|
168
|
-
package Test {
|
|
169
|
-
part def Stakeholder :> Actor {
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
`);
|
|
173
|
-
const pkg = model.members[0];
|
|
174
|
-
const partDef = pkg.members[0];
|
|
175
|
-
expect(partDef.name).toBe('Stakeholder');
|
|
176
|
-
expect(partDef.specialization?.superType).toBe('Actor');
|
|
177
|
-
});
|
|
178
|
-
it('parses part def with doc comment', async () => {
|
|
179
|
-
const model = await parseValid(`
|
|
180
|
-
package Test {
|
|
181
|
-
part def Actor {
|
|
182
|
-
doc /* A person or external system. */
|
|
183
|
-
attribute name : String;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
`);
|
|
187
|
-
const pkg = model.members[0];
|
|
188
|
-
const partDef = pkg.members[0];
|
|
189
|
-
expect(partDef.body).toHaveLength(2);
|
|
190
|
-
const doc = partDef.body[0];
|
|
191
|
-
expect(doc.$type).toBe('DocComment');
|
|
192
|
-
expect(doc.content).toContain('A person or external system.');
|
|
193
|
-
});
|
|
194
|
-
it('parses part def with nested part member', async () => {
|
|
195
|
-
const model = await parseValid(`
|
|
196
|
-
package Test {
|
|
197
|
-
part def System {
|
|
198
|
-
attribute name : String;
|
|
199
|
-
part subsystems : Subsystem[0..*];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
`);
|
|
203
|
-
const pkg = model.members[0];
|
|
204
|
-
const partDef = pkg.members[0];
|
|
205
|
-
expect(partDef.body).toHaveLength(2);
|
|
206
|
-
const partMember = partDef.body[1];
|
|
207
|
-
expect(partMember.$type).toBe('PartMember');
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe('RequirementDefinition', () => {
|
|
211
|
-
it('parses requirement def with attributes', async () => {
|
|
212
|
-
const model = await parseValid(`
|
|
213
|
-
package Test {
|
|
214
|
-
requirement def Requirement {
|
|
215
|
-
attribute reqId : String;
|
|
216
|
-
attribute title : String;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
`);
|
|
220
|
-
const pkg = model.members[0];
|
|
221
|
-
const reqDef = pkg.members[0];
|
|
222
|
-
expect(reqDef.$type).toBe('RequirementDefinition');
|
|
223
|
-
expect(reqDef.name).toBe('Requirement');
|
|
224
|
-
expect(reqDef.body).toHaveLength(2);
|
|
225
|
-
});
|
|
226
|
-
it('parses requirement def with specialization', async () => {
|
|
227
|
-
const model = await parseValid(`
|
|
228
|
-
package Test {
|
|
229
|
-
requirement def Hazard :> Requirement {
|
|
230
|
-
attribute hazardId : String;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
`);
|
|
234
|
-
const pkg = model.members[0];
|
|
235
|
-
const reqDef = pkg.members[0];
|
|
236
|
-
expect(reqDef.specialization?.superType).toBe('Requirement');
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
describe('ActionDefinition', () => {
|
|
240
|
-
it('parses action def', async () => {
|
|
241
|
-
const model = await parseValid(`
|
|
242
|
-
package Test {
|
|
243
|
-
action def SystemFunction {
|
|
244
|
-
attribute name : String;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
`);
|
|
248
|
-
const pkg = model.members[0];
|
|
249
|
-
const actionDef = pkg.members[0];
|
|
250
|
-
expect(actionDef.$type).toBe('ActionDefinition');
|
|
251
|
-
expect(actionDef.name).toBe('SystemFunction');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
describe('ConnectionDefinition', () => {
|
|
255
|
-
it('parses connection def with ends', async () => {
|
|
256
|
-
const model = await parseValid(`
|
|
257
|
-
package Test {
|
|
258
|
-
connection def Aggregation {
|
|
259
|
-
end whole[1];
|
|
260
|
-
end part[0..*];
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
`);
|
|
264
|
-
const pkg = model.members[0];
|
|
265
|
-
const connDef = pkg.members[0];
|
|
266
|
-
expect(connDef.$type).toBe('ConnectionDefinition');
|
|
267
|
-
expect(connDef.name).toBe('Aggregation');
|
|
268
|
-
expect(connDef.body).toHaveLength(2);
|
|
269
|
-
const end1 = connDef.body[0];
|
|
270
|
-
expect(end1.$type).toBe('EndDeclaration');
|
|
271
|
-
expect(end1.name).toBe('whole');
|
|
272
|
-
expect(end1.multiplicity?.exact).toBe(1); // Langium converts INT to number
|
|
273
|
-
const end2 = connDef.body[1];
|
|
274
|
-
expect(end2.name).toBe('part');
|
|
275
|
-
expect(end2.multiplicity?.lower).toBe(0); // Langium converts INT to number
|
|
276
|
-
expect(end2.multiplicity?.unbounded).toBe(true);
|
|
277
|
-
});
|
|
278
|
-
it('parses connection def with doc and ends', async () => {
|
|
279
|
-
const model = await parseValid(`
|
|
280
|
-
package Test {
|
|
281
|
-
connection def TraceTo {
|
|
282
|
-
doc /* Directed traceability. */
|
|
283
|
-
end source[1];
|
|
284
|
-
end target[1];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
`);
|
|
288
|
-
const pkg = model.members[0];
|
|
289
|
-
const connDef = pkg.members[0];
|
|
290
|
-
expect(connDef.body).toHaveLength(3);
|
|
291
|
-
expect(connDef.body[0].$type).toBe('DocComment');
|
|
292
|
-
expect(connDef.body[1].$type).toBe('EndDeclaration');
|
|
293
|
-
});
|
|
294
|
-
it('parses connection def with typed ends', async () => {
|
|
295
|
-
const model = await parseValid(`
|
|
296
|
-
package Test {
|
|
297
|
-
connection def Mitigates {
|
|
298
|
-
doc /* A risk control mitigates a hazard. */
|
|
299
|
-
end control : RiskControl [1];
|
|
300
|
-
end hazard : Hazard [1];
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
`);
|
|
304
|
-
const pkg = model.members[0];
|
|
305
|
-
const connDef = pkg.members[0];
|
|
306
|
-
expect(connDef.name).toBe('Mitigates');
|
|
307
|
-
expect(connDef.body).toHaveLength(3); // doc + 2 ends
|
|
308
|
-
const end1 = connDef.body[1];
|
|
309
|
-
expect(end1.$type).toBe('EndDeclaration');
|
|
310
|
-
expect(end1.name).toBe('control');
|
|
311
|
-
expect(end1.type).toBe('RiskControl');
|
|
312
|
-
expect(end1.multiplicity?.exact).toBe(1);
|
|
313
|
-
const end2 = connDef.body[2];
|
|
314
|
-
expect(end2.name).toBe('hazard');
|
|
315
|
-
expect(end2.type).toBe('Hazard');
|
|
316
|
-
expect(end2.multiplicity?.exact).toBe(1);
|
|
317
|
-
});
|
|
318
|
-
it('parses connection def with mixed typed and untyped ends', async () => {
|
|
319
|
-
const model = await parseValid(`
|
|
320
|
-
package Test {
|
|
321
|
-
connection def Satisfy {
|
|
322
|
-
end satisfiedBy[1];
|
|
323
|
-
end satisfies : Requirement [1];
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
`);
|
|
327
|
-
const pkg = model.members[0];
|
|
328
|
-
const connDef = pkg.members[0];
|
|
329
|
-
expect(connDef.body).toHaveLength(2);
|
|
330
|
-
const end1 = connDef.body[0];
|
|
331
|
-
expect(end1.name).toBe('satisfiedBy');
|
|
332
|
-
expect(end1.type).toBeUndefined();
|
|
333
|
-
expect(end1.multiplicity?.exact).toBe(1);
|
|
334
|
-
const end2 = connDef.body[1];
|
|
335
|
-
expect(end2.name).toBe('satisfies');
|
|
336
|
-
expect(end2.type).toBe('Requirement');
|
|
337
|
-
expect(end2.multiplicity?.exact).toBe(1);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
describe('EnumDefinition', () => {
|
|
341
|
-
it('parses enum def with literals', async () => {
|
|
342
|
-
const model = await parseValid(`
|
|
343
|
-
package Test {
|
|
344
|
-
enum def SeverityLevel {
|
|
345
|
-
enum Negligible;
|
|
346
|
-
enum Minor;
|
|
347
|
-
enum Serious;
|
|
348
|
-
enum Critical;
|
|
349
|
-
enum Catastrophic;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
`);
|
|
353
|
-
const pkg = model.members[0];
|
|
354
|
-
const enumDef = pkg.members[0];
|
|
355
|
-
expect(enumDef.$type).toBe('EnumDefinition');
|
|
356
|
-
expect(enumDef.name).toBe('SeverityLevel');
|
|
357
|
-
expect(enumDef.literals).toHaveLength(5);
|
|
358
|
-
expect(enumDef.literals[0].name).toBe('Negligible');
|
|
359
|
-
expect(enumDef.literals[4].name).toBe('Catastrophic');
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
describe('PortDefinition', () => {
|
|
363
|
-
it('parses port def with specialization', async () => {
|
|
364
|
-
const model = await parseValid(`
|
|
365
|
-
package Test {
|
|
366
|
-
port def Port { }
|
|
367
|
-
port def PortEthernet :> Port { }
|
|
368
|
-
}
|
|
369
|
-
`);
|
|
370
|
-
const pkg = model.members[0];
|
|
371
|
-
const portDef = pkg.members[1];
|
|
372
|
-
expect(portDef.name).toBe('PortEthernet');
|
|
373
|
-
expect(portDef.specialization?.superType).toBe('Port');
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
describe('InterfaceDefinition', () => {
|
|
377
|
-
it('parses interface def with specialization', async () => {
|
|
378
|
-
const model = await parseValid(`
|
|
379
|
-
package Test {
|
|
380
|
-
interface def SoftwareInterface :> Interface { }
|
|
381
|
-
}
|
|
382
|
-
`);
|
|
383
|
-
const pkg = model.members[0];
|
|
384
|
-
const ifDef = pkg.members[0];
|
|
385
|
-
expect(ifDef.$type).toBe('InterfaceDefinition');
|
|
386
|
-
expect(ifDef.name).toBe('SoftwareInterface');
|
|
387
|
-
expect(ifDef.specialization?.superType).toBe('Interface');
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
describe('AttributeDefinition', () => {
|
|
391
|
-
it('parses attribute def', async () => {
|
|
392
|
-
const model = await parseValid(`
|
|
393
|
-
package Test {
|
|
394
|
-
attribute def DataType { }
|
|
395
|
-
attribute def RosMessage :> DataType { }
|
|
396
|
-
}
|
|
397
|
-
`);
|
|
398
|
-
const pkg = model.members[0];
|
|
399
|
-
const attrDef = pkg.members[1];
|
|
400
|
-
expect(attrDef.$type).toBe('AttributeDefinition');
|
|
401
|
-
expect(attrDef.name).toBe('RosMessage');
|
|
402
|
-
expect(attrDef.specialization?.superType).toBe('DataType');
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
describe('Architecture fixture: methodology scope expressions', () => {
|
|
406
|
-
it('parses explicit scalar methodology scope entries', async () => {
|
|
407
|
-
const model = await parseValid(`
|
|
408
|
-
package scope {
|
|
409
|
-
part gpcaScope : MethodologyScope {
|
|
410
|
-
attribute includedArchLayer = "operational";
|
|
411
|
-
attribute includedArchLayer = "functional";
|
|
412
|
-
attribute includedStandard = "ISO 14971";
|
|
413
|
-
attribute excludedKind = "SOUPComponent";
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
`);
|
|
417
|
-
const pkg = model.members[0];
|
|
418
|
-
const scope = pkg.members[0];
|
|
419
|
-
expect(scope.body).toHaveLength(4);
|
|
420
|
-
});
|
|
421
|
-
it('parses set literals in attribute assignments', async () => {
|
|
422
|
-
const model = await parseValid(`
|
|
423
|
-
package scope {
|
|
424
|
-
part defaultLayerSet : MethodologyLayerSet {
|
|
425
|
-
attribute layers = {"operational", "functional"};
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
`);
|
|
429
|
-
const pkg = model.members[0];
|
|
430
|
-
const usage = pkg.members[0];
|
|
431
|
-
expect(usage.body).toHaveLength(1);
|
|
432
|
-
});
|
|
433
|
-
it('rejects set-difference expressions in attribute assignments', async () => {
|
|
434
|
-
const errors = await parseErrors(`
|
|
435
|
-
package scope {
|
|
436
|
-
part gpcaScope : MethodologyScope {
|
|
437
|
-
attribute includedArchLayers = defaultLayerSet.layers - {"cybersecurity"};
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
`);
|
|
441
|
-
expect(errors.length).toBeGreaterThan(0);
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
describe('Architecture fixture: view and presentation syntax', () => {
|
|
445
|
-
it('parses view def with scalar presentationKind fallback entries', async () => {
|
|
446
|
-
const model = await parseValid(`
|
|
447
|
-
package risk_management {
|
|
448
|
-
import memo::core::stdlib::*;
|
|
449
|
-
|
|
450
|
-
view def RiskMatrixView :> DiagramView {
|
|
451
|
-
attribute presentationKind = PresentationKind::riskTable;
|
|
452
|
-
attribute presentationKind = PresentationKind::matrix;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
`);
|
|
456
|
-
const pkg = model.members[0];
|
|
457
|
-
const viewDef = pkg.members[1];
|
|
458
|
-
expect(viewDef.$type).toBe('ViewDefinition');
|
|
459
|
-
expect(viewDef.name).toBe('RiskMatrixView');
|
|
460
|
-
expect(viewDef.specialization?.superType).toBe('DiagramView');
|
|
461
|
-
expect(viewDef.body).toHaveLength(2);
|
|
462
|
-
});
|
|
463
|
-
it('parses private import visibility modifiers', async () => {
|
|
464
|
-
const model = await parseValid(`
|
|
465
|
-
package risk_management {
|
|
466
|
-
private import memo::core::stdlib::*;
|
|
467
|
-
}
|
|
468
|
-
`);
|
|
469
|
-
const pkg = model.members[0];
|
|
470
|
-
expect(pkg.members).toHaveLength(1);
|
|
471
|
-
expect(pkg.members[0].$type).toBe('ImportDeclaration');
|
|
472
|
-
});
|
|
473
|
-
it('parses multiplicity on attribute type declarations', async () => {
|
|
474
|
-
const model = await parseValid(`
|
|
475
|
-
package risk_management {
|
|
476
|
-
view def RiskMatrixView :> DiagramView {
|
|
477
|
-
attribute presentationKind : PresentationKind[*];
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
`);
|
|
481
|
-
const pkg = model.members[0];
|
|
482
|
-
const viewDef = pkg.members[0];
|
|
483
|
-
expect(viewDef.body).toHaveLength(1);
|
|
484
|
-
});
|
|
485
|
-
it('parses collection-valued attribute assignments', async () => {
|
|
486
|
-
const model = await parseValid(`
|
|
487
|
-
package risk_management {
|
|
488
|
-
view def RiskMatrixView :> DiagramView {
|
|
489
|
-
attribute presentationKind = { PresentationKind::riskTable };
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
`);
|
|
493
|
-
const pkg = model.members[0];
|
|
494
|
-
const viewDef = pkg.members[0];
|
|
495
|
-
expect(viewDef.body).toHaveLength(1);
|
|
496
|
-
});
|
|
497
|
-
});
|
|
498
|
-
// ─── Usages ──────────────────────────────────────────────────────────────────
|
|
499
|
-
describe('PartUsage', () => {
|
|
500
|
-
it('parses part usage with attribute redefines', async () => {
|
|
501
|
-
const model = await parseValid(`
|
|
502
|
-
package Test {
|
|
503
|
-
part clinician : Actor {
|
|
504
|
-
attribute redefines name = "Clinician";
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
`);
|
|
508
|
-
const pkg = model.members[0];
|
|
509
|
-
const usage = pkg.members[0];
|
|
510
|
-
expect(usage.$type).toBe('PartUsage');
|
|
511
|
-
expect(usage.name).toBe('clinician');
|
|
512
|
-
expect(usage.type).toBe('Actor');
|
|
513
|
-
const attr = usage.body[0];
|
|
514
|
-
expect(attr.redefines).toBe(true);
|
|
515
|
-
expect(attr.name).toBe('name');
|
|
516
|
-
const val = attr.value;
|
|
517
|
-
expect(val.$type).toBe('StringValue');
|
|
518
|
-
expect(val.value).toBe('Clinician'); // Langium strips quotes
|
|
519
|
-
});
|
|
520
|
-
it('parses part usage with integer attribute', async () => {
|
|
521
|
-
const model = await parseValid(`
|
|
522
|
-
package Test {
|
|
523
|
-
part mainProcessor : Microcontroller {
|
|
524
|
-
attribute redefines numberOfCores = 1;
|
|
525
|
-
attribute redefines ram = 256;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
`);
|
|
529
|
-
const pkg = model.members[0];
|
|
530
|
-
const usage = pkg.members[0];
|
|
531
|
-
const attr = usage.body[0];
|
|
532
|
-
const val = attr.value;
|
|
533
|
-
expect(val.$type).toBe('IntValue');
|
|
534
|
-
expect(val.value).toBe(1); // Langium converts INT to number
|
|
535
|
-
});
|
|
536
|
-
it('parses part usage with boolean attribute', async () => {
|
|
537
|
-
const model = await parseValid(`
|
|
538
|
-
package Test {
|
|
539
|
-
part c : Catheter {
|
|
540
|
-
attribute redefines isAblation = true;
|
|
541
|
-
attribute redefines isDiagnostic = false;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
`);
|
|
545
|
-
const pkg = model.members[0];
|
|
546
|
-
const usage = pkg.members[0];
|
|
547
|
-
const attr1 = usage.body[0];
|
|
548
|
-
expect(attr1.value.value).toBe('true');
|
|
549
|
-
const attr2 = usage.body[1];
|
|
550
|
-
expect(attr2.value.value).toBe('false');
|
|
551
|
-
});
|
|
552
|
-
it('parses part usage with enum attribute value', async () => {
|
|
553
|
-
const model = await parseValid(`
|
|
554
|
-
package Test {
|
|
555
|
-
requirement harmAdverseDrugReaction : Harm {
|
|
556
|
-
attribute redefines severity = SeverityLevel::Catastrophic;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
`);
|
|
560
|
-
const pkg = model.members[0];
|
|
561
|
-
const usage = pkg.members[0];
|
|
562
|
-
const attr = usage.body[0];
|
|
563
|
-
const val = attr.value;
|
|
564
|
-
expect(val.$type).toBe('EnumValue');
|
|
565
|
-
expect(val.enumRef).toBe('SeverityLevel::Catastrophic');
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
describe('RequirementUsage', () => {
|
|
569
|
-
it('parses requirement usage with doc comment', async () => {
|
|
570
|
-
const model = await parseValid(`
|
|
571
|
-
package Test {
|
|
572
|
-
requirement unAccurateDelivery : UserNeed {
|
|
573
|
-
attribute redefines reqId = "UN-001";
|
|
574
|
-
attribute redefines title = "Accurate Medication Delivery";
|
|
575
|
-
doc /* The clinician needs the pump to deliver medication. */
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
`);
|
|
579
|
-
const pkg = model.members[0];
|
|
580
|
-
const usage = pkg.members[0];
|
|
581
|
-
expect(usage.$type).toBe('RequirementUsage');
|
|
582
|
-
expect(usage.name).toBe('unAccurateDelivery');
|
|
583
|
-
expect(usage.type).toBe('UserNeed');
|
|
584
|
-
expect(usage.body).toHaveLength(3);
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
describe('ActionUsage', () => {
|
|
588
|
-
it('parses action usage', async () => {
|
|
589
|
-
const model = await parseValid(`
|
|
590
|
-
package Test {
|
|
591
|
-
action sfRegulateFlow : SystemFunction {
|
|
592
|
-
attribute redefines name = "Regulate Flow Rate";
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
`);
|
|
596
|
-
const pkg = model.members[0];
|
|
597
|
-
const usage = pkg.members[0];
|
|
598
|
-
expect(usage.$type).toBe('ActionUsage');
|
|
599
|
-
expect(usage.name).toBe('sfRegulateFlow');
|
|
600
|
-
expect(usage.type).toBe('SystemFunction');
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
describe('PortUsage', () => {
|
|
604
|
-
it('parses port usage with empty body', async () => {
|
|
605
|
-
const model = await parseValid(`
|
|
606
|
-
package Test {
|
|
607
|
-
port pressureSensorPort : Port { }
|
|
608
|
-
port networkPort : PortEthernet { }
|
|
609
|
-
}
|
|
610
|
-
`);
|
|
611
|
-
const pkg = model.members[0];
|
|
612
|
-
expect(pkg.members).toHaveLength(2);
|
|
613
|
-
const p1 = pkg.members[0];
|
|
614
|
-
expect(p1.name).toBe('pressureSensorPort');
|
|
615
|
-
});
|
|
616
|
-
it('parses conjugated port usage with ~ marker', async () => {
|
|
617
|
-
const model = await parseValid(`
|
|
618
|
-
package Test {
|
|
619
|
-
port ~sensorIn : SensorPort;
|
|
620
|
-
}
|
|
621
|
-
`);
|
|
622
|
-
const pkg = model.members[0];
|
|
623
|
-
const p = pkg.members[0];
|
|
624
|
-
expect(p.$type).toBe('PortUsage');
|
|
625
|
-
expect(p.isConjugated).toBe(true);
|
|
626
|
-
expect(p.name).toBe('sensorIn');
|
|
627
|
-
expect(p.type).toBe('SensorPort');
|
|
628
|
-
});
|
|
629
|
-
it('parses directed port usage with in/out/inout', async () => {
|
|
630
|
-
const model = await parseValid(`
|
|
631
|
-
package Test {
|
|
632
|
-
part def Pump {
|
|
633
|
-
in port sensorIn : SensorPort;
|
|
634
|
-
out port controlOut : ControlPort;
|
|
635
|
-
inout port diagPort : DiagPort;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
`);
|
|
639
|
-
const pkg = model.members[0];
|
|
640
|
-
const partDef = pkg.members[0];
|
|
641
|
-
expect(partDef.body).toHaveLength(3);
|
|
642
|
-
const p1 = partDef.body[0];
|
|
643
|
-
expect(p1.$type).toBe('PortUsage');
|
|
644
|
-
expect(p1.direction).toBe('in');
|
|
645
|
-
expect(p1.name).toBe('sensorIn');
|
|
646
|
-
const p2 = partDef.body[1];
|
|
647
|
-
expect(p2.direction).toBe('out');
|
|
648
|
-
expect(p2.name).toBe('controlOut');
|
|
649
|
-
const p3 = partDef.body[2];
|
|
650
|
-
expect(p3.direction).toBe('inout');
|
|
651
|
-
expect(p3.name).toBe('diagPort');
|
|
652
|
-
});
|
|
653
|
-
it('parses conjugated + directed port usage', async () => {
|
|
654
|
-
const model = await parseValid(`
|
|
655
|
-
package Test {
|
|
656
|
-
in port ~conjugatedIn : SensorPort;
|
|
657
|
-
}
|
|
658
|
-
`);
|
|
659
|
-
const pkg = model.members[0];
|
|
660
|
-
const p = pkg.members[0];
|
|
661
|
-
expect(p.direction).toBe('in');
|
|
662
|
-
expect(p.isConjugated).toBe(true);
|
|
663
|
-
expect(p.name).toBe('conjugatedIn');
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
// ─── M-1 Combined fixture: port def + interface def + connect + flow + ~ ────
|
|
667
|
-
describe('M-1 fixture: ports and interfaces', () => {
|
|
668
|
-
it('parses all port/interface constructs in one model', async () => {
|
|
669
|
-
const model = await parseValid(`
|
|
670
|
-
package PortsAndInterfaces {
|
|
671
|
-
// Definitions
|
|
672
|
-
port def SensorPort {
|
|
673
|
-
attribute dataRate : String;
|
|
674
|
-
}
|
|
675
|
-
port def ControlPort :> SensorPort;
|
|
676
|
-
interface def SensorInterface {
|
|
677
|
-
attribute protocol : String;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// Part with directed and conjugated ports
|
|
681
|
-
part def PumpController {
|
|
682
|
-
in port sensorIn : SensorPort;
|
|
683
|
-
out port ~controlOut : ControlPort;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
part def SensorModule {
|
|
687
|
-
out port sensorOut : SensorPort;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// Action with flow
|
|
691
|
-
action def InfusionControl {
|
|
692
|
-
in prescription : DrugOrder;
|
|
693
|
-
out rate : FlowRate;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
action controlLoop : InfusionControl {
|
|
697
|
-
action receive : ReceiveOrder;
|
|
698
|
-
action regulate : RegulateFlow;
|
|
699
|
-
flow of DrugOrder from receive.prescription to regulate.order;
|
|
700
|
-
first start then receive then regulate then done;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Connection usage
|
|
704
|
-
connection : SensorLink connect source ::> sensorModule to target ::> pumpController;
|
|
705
|
-
|
|
706
|
-
// Port usages at package level
|
|
707
|
-
port mainSensor : SensorPort;
|
|
708
|
-
in port ~conjugatedDiag : SensorPort;
|
|
709
|
-
}
|
|
710
|
-
`);
|
|
711
|
-
const pkg = model.members[0];
|
|
712
|
-
expect(pkg.name).toBe('PortsAndInterfaces');
|
|
713
|
-
// Verify we got port defs, interface def, part defs with ports,
|
|
714
|
-
// action def, action usage with flow, connection, and port usages
|
|
715
|
-
const types = pkg.members.map((m) => m.$type);
|
|
716
|
-
expect(types).toContain('PortDefinition');
|
|
717
|
-
expect(types).toContain('InterfaceDefinition');
|
|
718
|
-
expect(types).toContain('PartDefinition');
|
|
719
|
-
expect(types).toContain('ActionDefinition');
|
|
720
|
-
expect(types).toContain('ActionUsage');
|
|
721
|
-
expect(types).toContain('ConnectionUsage');
|
|
722
|
-
expect(types).toContain('PortUsage');
|
|
723
|
-
// Verify conjugated port
|
|
724
|
-
const portUsages = pkg.members.filter((m) => m.$type === 'PortUsage');
|
|
725
|
-
const conjugated = portUsages.find((p) => p.isConjugated);
|
|
726
|
-
expect(conjugated).toBeDefined();
|
|
727
|
-
expect(conjugated.name).toBe('conjugatedDiag');
|
|
728
|
-
expect(conjugated.direction).toBe('in');
|
|
729
|
-
});
|
|
730
|
-
});
|
|
731
|
-
// ─── Connection usage ────────────────────────────────────────────────────────
|
|
732
|
-
describe('ConnectionUsage', () => {
|
|
733
|
-
it('parses typed connection usage', async () => {
|
|
734
|
-
const model = await parseValid(`
|
|
735
|
-
package Test {
|
|
736
|
-
connection : Mitigates connect control ::> rcFlowRateLimiter to hazard ::> hazOverInfusion;
|
|
737
|
-
}
|
|
738
|
-
`);
|
|
739
|
-
const pkg = model.members[0];
|
|
740
|
-
const conn = pkg.members[0];
|
|
741
|
-
expect(conn.$type).toBe('ConnectionUsage');
|
|
742
|
-
expect(conn.type).toBe('Mitigates');
|
|
743
|
-
expect(conn.source.endName).toBe('control');
|
|
744
|
-
expect(conn.source.ref).toBe('rcFlowRateLimiter');
|
|
745
|
-
expect(conn.target.endName).toBe('hazard');
|
|
746
|
-
expect(conn.target.ref).toBe('hazOverInfusion');
|
|
747
|
-
});
|
|
748
|
-
it('parses TraceTo connection', async () => {
|
|
749
|
-
const model = await parseValid(`
|
|
750
|
-
package Test {
|
|
751
|
-
connection : TraceTo connect source ::> unAccurateDelivery to target ::> sysReqFlowAccuracy;
|
|
752
|
-
}
|
|
753
|
-
`);
|
|
754
|
-
const pkg = model.members[0];
|
|
755
|
-
const conn = pkg.members[0];
|
|
756
|
-
expect(conn.type).toBe('TraceTo');
|
|
757
|
-
expect(conn.source.endName).toBe('source');
|
|
758
|
-
expect(conn.target.endName).toBe('target');
|
|
759
|
-
});
|
|
760
|
-
it('parses AllocateTo connection', async () => {
|
|
761
|
-
const model = await parseValid(`
|
|
762
|
-
package Test {
|
|
763
|
-
connection : AllocateTo connect function ::> sfRegulateFlow to structure ::> flowController;
|
|
764
|
-
}
|
|
765
|
-
`);
|
|
766
|
-
const pkg = model.members[0];
|
|
767
|
-
const conn = pkg.members[0];
|
|
768
|
-
expect(conn.type).toBe('AllocateTo');
|
|
769
|
-
expect(conn.source.endName).toBe('function');
|
|
770
|
-
expect(conn.target.endName).toBe('structure');
|
|
771
|
-
});
|
|
772
|
-
});
|
|
773
|
-
// ─── Full file integration tests ─────────────────────────────────────────────
|
|
774
|
-
describe('Integration: Ontology subset', () => {
|
|
775
|
-
it('parses a representative ontology subset', async () => {
|
|
776
|
-
const model = await parseValid(`
|
|
777
|
-
package MEMO_Ontology {
|
|
778
|
-
|
|
779
|
-
// Business Analysis
|
|
780
|
-
part def Actor {
|
|
781
|
-
doc /* A person or external system that interacts with the system. */
|
|
782
|
-
attribute name : String;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
part def Stakeholder :> Actor {
|
|
786
|
-
doc /* A party with interest. */
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Requirements
|
|
790
|
-
enum def RequirementsCategory {
|
|
791
|
-
enum Functional;
|
|
792
|
-
enum Performance;
|
|
793
|
-
enum Safety;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
requirement def Requirement {
|
|
797
|
-
attribute reqId : String;
|
|
798
|
-
attribute title : String;
|
|
799
|
-
attribute category : RequirementsCategory;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
requirement def Hazard {
|
|
803
|
-
attribute hazardId : String;
|
|
804
|
-
attribute title : String;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Functional
|
|
808
|
-
action def SystemFunction {
|
|
809
|
-
attribute name : String;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Interfaces
|
|
813
|
-
port def Port { }
|
|
814
|
-
port def PortEthernet :> Port { }
|
|
815
|
-
|
|
816
|
-
interface def Interface { }
|
|
817
|
-
interface def SoftwareInterface :> Interface { }
|
|
818
|
-
|
|
819
|
-
// Connections
|
|
820
|
-
connection def TraceTo {
|
|
821
|
-
doc /* Directed traceability. */
|
|
822
|
-
end source[1];
|
|
823
|
-
end target[1];
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
connection def Aggregation {
|
|
827
|
-
end whole[1];
|
|
828
|
-
end part[0..*];
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Cross-cutting
|
|
832
|
-
attribute def DataType { }
|
|
833
|
-
}
|
|
834
|
-
`);
|
|
835
|
-
const pkg = model.members[0];
|
|
836
|
-
expect(pkg.name).toBe('MEMO_Ontology');
|
|
837
|
-
// Count all members (definitions)
|
|
838
|
-
expect(pkg.members.length).toBeGreaterThanOrEqual(12);
|
|
839
|
-
});
|
|
840
|
-
});
|
|
841
|
-
describe('Integration: Device model subset', () => {
|
|
842
|
-
it('parses a representative infusion pump subset', async () => {
|
|
843
|
-
const model = await parseValid(`
|
|
844
|
-
package InfusionPump {
|
|
845
|
-
import MEMO_Ontology_Medical::*;
|
|
846
|
-
|
|
847
|
-
// Business layer
|
|
848
|
-
part clinician : Actor {
|
|
849
|
-
attribute redefines name = "Clinician";
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
part patient : Stakeholder {
|
|
853
|
-
attribute redefines name = "Patient";
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// Requirements
|
|
857
|
-
requirement unAccurateDelivery : UserNeed {
|
|
858
|
-
attribute redefines reqId = "UN-001";
|
|
859
|
-
attribute redefines title = "Accurate Medication Delivery";
|
|
860
|
-
doc /* The clinician needs the pump to deliver medication
|
|
861
|
-
* at the prescribed rate. */
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
requirement sysReqFlowAccuracy : SystemRequirement {
|
|
865
|
-
attribute redefines reqId = "SYS-REQ-001";
|
|
866
|
-
attribute redefines title = "Flow Rate Accuracy";
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Risk
|
|
870
|
-
requirement hazOverInfusion : Hazard {
|
|
871
|
-
attribute redefines hazardId = "HAZ-001";
|
|
872
|
-
attribute redefines title = "Over-Infusion";
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
requirement rcFlowRateLimiter : RiskControl {
|
|
876
|
-
attribute redefines rcId = "RC-001";
|
|
877
|
-
attribute redefines title = "Software Flow Rate Limiter";
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
// Functional
|
|
881
|
-
action sfRegulateFlow : SystemFunction {
|
|
882
|
-
attribute redefines name = "Regulate Flow Rate";
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Logical
|
|
886
|
-
part infusionPumpSystem : System {
|
|
887
|
-
attribute redefines name = "Infusion Pump System";
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// Physical
|
|
891
|
-
part mainProcessor : Microcontroller {
|
|
892
|
-
attribute redefines name = "Main Processor";
|
|
893
|
-
attribute redefines numberOfCores = 1;
|
|
894
|
-
attribute redefines ram = 256;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Software
|
|
898
|
-
part flowController : SoftwareComponent {
|
|
899
|
-
attribute redefines name = "Flow Rate Controller";
|
|
900
|
-
attribute redefines safetyClassification = "C";
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// Ports
|
|
904
|
-
port pressureSensorPort : Port { }
|
|
905
|
-
port networkPort : PortEthernet { }
|
|
906
|
-
|
|
907
|
-
// Interfaces
|
|
908
|
-
interface def PressureDataInterface :> SoftwareInterface { }
|
|
909
|
-
|
|
910
|
-
// UI
|
|
911
|
-
part mainScreen : UIScreen {
|
|
912
|
-
attribute redefines name = "Main Infusion Screen";
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Verification
|
|
916
|
-
part testFlowAccuracy : Test {
|
|
917
|
-
attribute redefines testId = "TC-001";
|
|
918
|
-
attribute redefines name = "Flow Rate Accuracy Verification";
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// Traceability connections
|
|
922
|
-
connection : TraceTo connect source ::> unAccurateDelivery to target ::> sysReqFlowAccuracy;
|
|
923
|
-
connection : Mitigates connect control ::> rcFlowRateLimiter to hazard ::> hazOverInfusion;
|
|
924
|
-
connection : AllocateTo connect function ::> sfRegulateFlow to structure ::> flowController;
|
|
925
|
-
connection : Satisfy connect satisfiedBy ::> flowController to satisfies ::> sysReqFlowAccuracy;
|
|
926
|
-
connection : Verify connect verifiedBy ::> testFlowAccuracy to verifies ::> sysReqFlowAccuracy;
|
|
927
|
-
}
|
|
928
|
-
`);
|
|
929
|
-
const pkg = model.members[0];
|
|
930
|
-
expect(pkg.name).toBe('InfusionPump');
|
|
931
|
-
// Count different member types
|
|
932
|
-
const imports = pkg.members.filter(m => m.$type === 'ImportDeclaration');
|
|
933
|
-
const connections = pkg.members.filter(m => m.$type === 'ConnectionUsage');
|
|
934
|
-
expect(imports).toHaveLength(1);
|
|
935
|
-
expect(connections).toHaveLength(5);
|
|
936
|
-
});
|
|
937
|
-
});
|
|
938
|
-
//# sourceMappingURL=parser.test.js.map
|