Skip to content

Commit 1ae5049

Browse files
committed
feat(semver-ranges): support x semver ranges in metamodel
Signed-off-by: Moh <[email protected]>
1 parent d752b0f commit 1ae5049

File tree

2 files changed

+121
-54
lines changed

2 files changed

+121
-54
lines changed

lib/metamodelutil.js

Lines changed: 118 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
'use strict';
1616

17+
const semver = require('semver');
18+
1719
/**
1820
* The metamodel itself, as an AST.
1921
* @type unknown
@@ -37,7 +39,54 @@ const metaModelCto = require('./metamodel.js');
3739
* @return {*} the model
3840
*/
3941
function findNamespace(priorModels, namespace) {
40-
return priorModels.models.find((thisModel) => thisModel.namespace === namespace);
42+
// Exact match fast-path
43+
const exact = priorModels.models.find(
44+
(thisModel) => thisModel.namespace === namespace
45+
);
46+
if (exact) {
47+
return exact;
48+
}
49+
50+
// Parse "name@versionOrRange"
51+
const atIndex = namespace.lastIndexOf('@');
52+
if (atIndex <= 0) {
53+
return undefined;
54+
}
55+
const baseName = namespace.substring(0, atIndex);
56+
const wanted = namespace.substring(atIndex + 1);
57+
58+
// Collect candidates with the same base name
59+
const candidates = priorModels.models
60+
.filter((m) => {
61+
const idx = m.namespace.lastIndexOf('@');
62+
if (idx <= 0) {
63+
return false;
64+
}
65+
const name = m.namespace.substring(0, idx);
66+
return name === baseName;
67+
})
68+
.map((m) => {
69+
const v = m.namespace.substring(m.namespace.lastIndexOf('@') + 1);
70+
return { model: m, version: v };
71+
});
72+
73+
if (candidates.length === 0) {
74+
return undefined;
75+
}
76+
77+
// Treat requested as a range (supports x-ranges like 1.x, 1.1.x)
78+
// Pick the highest version that satisfies the range
79+
const satisfying = candidates
80+
.filter((c) => semver.valid(c.version))
81+
.filter((c) =>
82+
semver.satisfies(c.version, wanted, {
83+
includePrerelease: false,
84+
loose: true,
85+
})
86+
)
87+
.sort((a, b) => semver.rcompare(a.version, b.version));
88+
89+
return satisfying.length > 0 ? satisfying[0].model : undefined;
4190
}
4291

4392
/**
@@ -59,11 +108,11 @@ function findDeclaration(thisModel, name) {
59108
function createNameTable(priorModels, metaModel) {
60109
const concertoNs = '[email protected]';
61110
const table = {
62-
'Concept': {namespace: concertoNs, name: 'Concept'},
63-
'Asset': {namespace: concertoNs, name: 'Asset'},
64-
'Participant': {namespace: concertoNs, name: 'Participant'},
65-
'Transaction': {namespace: concertoNs, name: 'Transaction'},
66-
'Event': {namespace: concertoNs, name: 'Event'},
111+
Concept: { namespace: concertoNs, name: 'Concept' },
112+
Asset: { namespace: concertoNs, name: 'Asset' },
113+
Participant: { namespace: concertoNs, name: 'Participant' },
114+
Transaction: { namespace: concertoNs, name: 'Transaction' },
115+
Event: { namespace: concertoNs, name: 'Event' },
67116
};
68117

69118
// First list the imported names in order (overriding as we go along)
@@ -72,13 +121,17 @@ function createNameTable(priorModels, metaModel) {
72121
const modelFile = findNamespace(priorModels, namespace);
73122
if (imp.$class === `${MetaModelNamespace}.ImportType`) {
74123
if (!findDeclaration(modelFile, imp.name)) {
75-
throw new Error(`Declaration ${imp.name} in namespace ${namespace} not found`);
124+
throw new Error(
125+
`Declaration ${imp.name} in namespace ${namespace} not found`
126+
);
76127
}
77-
table[imp.name] = {namespace, name: imp.name};
128+
table[imp.name] = { namespace, name: imp.name };
78129
} else if (imp.$class === `${MetaModelNamespace}.ImportTypes`) {
79130
// Create a map of aliased types if they exist, otherwise initialize an empty map.
80131
const aliasedMap = imp.aliasedTypes
81-
? new Map(imp.aliasedTypes.map(({ name, aliasedName }) => [name, aliasedName]))
132+
? new Map(
133+
imp.aliasedTypes.map(({ name, aliasedName }) => [name, aliasedName])
134+
)
82135
: new Map();
83136
imp.types.forEach((type) => {
84137
// 'localName' is the identifier used to refer to the imported type, as it can be aliased..
@@ -87,20 +140,25 @@ function createNameTable(priorModels, metaModel) {
87140
// Verify if the type declaration exists in the model file.
88141
// Here, 'type' refers to the actual declaration name within the model file that is being imported.
89142
if (!findDeclaration(modelFile, type)) {
90-
throw new Error(`Declaration ${type} in namespace ${namespace} not found`);
143+
throw new Error(
144+
`Declaration ${type} in namespace ${namespace} not found`
145+
);
91146
}
92-
table[localName] = localName !== type ? {namespace, name: localName, resolvedName: type} : {namespace, name: type};
147+
table[localName] =
148+
localName !== type
149+
? { namespace, name: localName, resolvedName: type }
150+
: { namespace, name: type };
93151
});
94152
} else {
95153
(modelFile.declarations || []).forEach((decl) => {
96-
table[decl.name] = {namespace, name: decl.name};
154+
table[decl.name] = { namespace, name: decl.name };
97155
});
98156
}
99157
});
100158

101159
// Then add the names local to this metaModel (overriding as we go along)
102160
(metaModel.declarations || []).forEach((decl) => {
103-
table[decl.name] = {namespace: metaModel.namespace, name: decl.name};
161+
table[decl.name] = { namespace: metaModel.namespace, name: decl.name };
104162
});
105163

106164
return table;
@@ -132,64 +190,70 @@ function resolveTypeNames(metaModel, table) {
132190
});
133191

134192
switch (metaModel.$class) {
135-
case `${MetaModelNamespace}.Model`: {
136-
(metaModel.declarations || []).forEach((decl) => {
137-
resolveTypeNames(decl, table);
138-
});
139-
}
193+
case `${MetaModelNamespace}.Model`:
194+
{
195+
(metaModel.declarations || []).forEach((decl) => {
196+
resolveTypeNames(decl, table);
197+
});
198+
}
140199
break;
141200
case `${MetaModelNamespace}.EnumDeclaration`:
142201
case `${MetaModelNamespace}.AssetDeclaration`:
143202
case `${MetaModelNamespace}.ConceptDeclaration`:
144203
case `${MetaModelNamespace}.EventDeclaration`:
145204
case `${MetaModelNamespace}.TransactionDeclaration`:
146-
case `${MetaModelNamespace}.ParticipantDeclaration`: {
147-
if (metaModel.superType) {
148-
const name = metaModel.superType.name;
149-
metaModel.superType.namespace = resolveName(name, table);
150-
metaModel.superType.name = table[name].name;
151-
if (table[name]?.resolvedName) {
152-
metaModel.superType.resolvedName = table[name].resolvedName;
205+
case `${MetaModelNamespace}.ParticipantDeclaration`:
206+
{
207+
if (metaModel.superType) {
208+
const name = metaModel.superType.name;
209+
metaModel.superType.namespace = resolveName(name, table);
210+
metaModel.superType.name = table[name].name;
211+
if (table[name]?.resolvedName) {
212+
metaModel.superType.resolvedName = table[name].resolvedName;
213+
}
153214
}
215+
(metaModel.properties || []).forEach((property) => {
216+
resolveTypeNames(property, table);
217+
});
154218
}
155-
(metaModel.properties || []).forEach((property) => {
156-
resolveTypeNames(property, table);
157-
});
158-
}
159219
break;
160-
case `${MetaModelNamespace}.MapDeclaration`: {
161-
resolveTypeNames(metaModel.key, table);
162-
resolveTypeNames(metaModel.value, table);
163-
}
220+
case `${MetaModelNamespace}.MapDeclaration`:
221+
{
222+
resolveTypeNames(metaModel.key, table);
223+
resolveTypeNames(metaModel.value, table);
224+
}
164225
break;
165-
case `${MetaModelNamespace}.Decorator`: {
166-
(metaModel.arguments || []).forEach((argument) => {
167-
resolveTypeNames(argument, table);
168-
});
169-
}
226+
case `${MetaModelNamespace}.Decorator`:
227+
{
228+
(metaModel.arguments || []).forEach((argument) => {
229+
resolveTypeNames(argument, table);
230+
});
231+
}
170232
break;
171233
case `${MetaModelNamespace}.ObjectProperty`:
172234
case `${MetaModelNamespace}.RelationshipProperty`:
173235
case `${MetaModelNamespace}.DecoratorTypeReference`:
174236
case `${MetaModelNamespace}.ObjectMapKeyType`:
175237
case `${MetaModelNamespace}.ObjectMapValueType`:
176-
case `${MetaModelNamespace}.RelationshipMapValueType`: {
177-
metaModel.type.namespace = resolveName(metaModel.type.name, table);
178-
metaModel.type.name = table[metaModel.type.name].name;
179-
if (table[metaModel.type.name]?.resolvedName) {
180-
metaModel.type.resolvedName = table[metaModel.type.name].resolvedName;
238+
case `${MetaModelNamespace}.RelationshipMapValueType`:
239+
{
240+
metaModel.type.namespace = resolveName(metaModel.type.name, table);
241+
metaModel.type.name = table[metaModel.type.name].name;
242+
if (table[metaModel.type.name]?.resolvedName) {
243+
metaModel.type.resolvedName = table[metaModel.type.name].resolvedName;
244+
}
181245
}
182-
}
183246
break;
184247
case `${MetaModelNamespace}.StringScalar`:
185248
case `${MetaModelNamespace}.BooleanScalar`:
186249
case `${MetaModelNamespace}.DateTimeScalar`:
187250
case `${MetaModelNamespace}.DoubleScalar`:
188251
case `${MetaModelNamespace}.LongScalar`:
189-
case `${MetaModelNamespace}.IntegerScalar`: {
190-
metaModel.namespace = resolveName(metaModel.name, table);
191-
metaModel.name = table[metaModel.name].name;
192-
}
252+
case `${MetaModelNamespace}.IntegerScalar`:
253+
{
254+
metaModel.namespace = resolveName(metaModel.name, table);
255+
metaModel.name = table[metaModel.name].name;
256+
}
193257
break;
194258
}
195259
return metaModel;
@@ -242,11 +306,12 @@ function importFullyQualifiedNames(imp) {
242306
case `${MetaModelNamespace}.ImportType`:
243307
result.push(`${imp.namespace}.${imp.name}`);
244308
break;
245-
case `${MetaModelNamespace}.ImportTypes`: {
246-
imp.types.forEach(type => {
247-
result.push(`${imp.namespace}.${type}`);
248-
});
249-
}
309+
case `${MetaModelNamespace}.ImportTypes`:
310+
{
311+
imp.types.forEach((type) => {
312+
result.push(`${imp.namespace}.${type}`);
313+
});
314+
}
250315
break;
251316
default:
252317
throw new Error(`Unrecognized imports ${imp.$class}`);

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
],
3838
"author": "accordproject.org",
3939
"license": "Apache-2.0",
40+
"dependencies": {
41+
"semver": "7.6.3"
42+
},
4043
"devDependencies": {
4144
"@types/webgl-ext": "0.0.37",
4245
"chai": "4.3.6",
@@ -47,7 +50,6 @@
4750
"license-check-and-add": "2.3.6",
4851
"mocha": "10.8.2",
4952
"nyc": "17.1.0",
50-
"semver": "7.6.3",
5153
"typescript": "5.7.2"
5254
},
5355
"browserslist": "> 0.25%, not dead",

0 commit comments

Comments
 (0)