Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13,134 changes: 3,986 additions & 9,148 deletions client/src/data/templates.json

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions client/tests/data/options-TestTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,20 @@
"definition": false,
"shortExclType": false
},
"TestPackage.Template.TestTemplate.dead_end_component": {
"modelicaPath": "TestPackage.Template.TestTemplate.dead_end_component",
"type": "TestPackage.Component.DeadEndComponent",
"name": "Dead-end component",
"group": "Skipped Group",
"tab": "Skipped Tab",
"visible": false,
"enable": false,
"modifiers": {},
"replaceable": false,
"options": [],
"definition": false,
"shortExclType": false
},
"TestPackage.Template.TestTemplate.typ": {
"modelicaPath": "TestPackage.Template.TestTemplate.typ",
"type": "TestPackage.Types.IceCream",
Expand Down Expand Up @@ -1073,6 +1087,22 @@
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param": {
"final": true,
"redeclare": "TestPackage.Component.SecondComponent"
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.component_param": {
"expression": "\"From final redeclare\"",
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.is_another_param": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.inner_outer_param": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.container": {
"final": true
},
"TestPackage.Template.TestTemplate.redeclare_param_01.replaceable_param.icecream": {
"final": true
}
},
"replaceable": false,
Expand Down Expand Up @@ -1538,6 +1568,8 @@
"TestPackage.Template.TestTemplate.disabled_component",
"TestPackage.Template.TestTemplate.connector_param",
"TestPackage.Template.TestTemplate.connector_param_false",
"TestPackage.Template.TestTemplate.linkage_keyword_false",
"TestPackage.Template.TestTemplate.dead_end_component",
"TestPackage.Template.TestTemplate.typ",
"TestPackage.Template.TestTemplate.typ_limited",
"TestPackage.Template.TestTemplate.final_group_member_bool",
Expand Down
Binary file modified client/tests/data/templates.json.gz
Binary file not shown.
92 changes: 87 additions & 5 deletions server/src/parser/modification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,53 @@ export function createModification(
* - `redeclare NewType myParam = someValue` -> redeclare="NewType", value="someValue"
* - `redeclare package Medium = NewMedium` -> redeclare="NewMedium", value=undefined
*/
/**
* A `final` redeclare locks the component to its redeclared type: nothing
* downstream can touch it again, so any bindings written inline in that same
* redeclare clause are just as unmodifiable even when they don't repeat the
* `final` keyword themselves (Buildings authors are inconsistent about this).
*/
function markModsFinal(mods: Modification[]): void {
mods.forEach((m) => {
m.final = true;
if (m.mods.length) {
markModsFinal(m.mods);
}
});
}

/**
* A `final` redeclare locks down every parameter of the redeclared type, not
* just the ones bound inline in the redeclare clause: marks the already
* present child mods final, then synthesizes an unbound `final` Modification
* for every other own/inherited parameter of `element` so those can't be
* modified downstream either.
*/
function finalizeAllParams(
element: Element | undefined,
childBasePath: string,
childMods: Modification[],
): void {
markModsFinal(childMods);

const boundNames = new Set(childMods.map((m) => m.name));
const getChildElements = (element as { getChildElements?: () => Element[] })
?.getChildElements;
const allParams =
typeof getChildElements === "function"
? getChildElements.call(element)
: [];

allParams.forEach((el) => {
const paramName = el.modelicaPath.split(".").pop() as string;
if (!boundNames.has(paramName)) {
childMods.push(
new Modification(childBasePath, paramName, undefined, [], true),
);
}
});
}

function unpackRedeclaration(props: ModificationProps) {
let { basePath, definition, baseType } = props;
let redeclaration = (definition as mj.RedeclareMod).element_redeclaration;
Expand Down Expand Up @@ -184,6 +231,14 @@ function unpackRedeclaration(props: ModificationProps) {
}
}

if (final) {
finalizeAllParams(
element,
[basePath, name].filter((s) => s).join("."),
childMods,
);
}

// The redeclared type is stored under 'redeclare' property
const redeclaredType = element.type;

Expand Down Expand Up @@ -226,6 +281,14 @@ function unpackRedeclaration(props: ModificationProps) {
);
}

if (final) {
finalizeAllParams(
aliasedType,
[basePath, name].filter((s) => s).join("."),
childMods,
);
}

return new Modification(
basePath,
name,
Expand Down Expand Up @@ -349,17 +412,36 @@ function unpackModblock(props: ModificationProps) {
?.name;
const constrainingClause = replaceable.constraining_clause;
const replaceableType = typeStore.get(typeSpecifier, basePath);
value = replaceableType?.modelicaPath; // modelicaPath is the replaceable type
if (!replaceableType) {
// PUNCH-OUT! The choice references a class that cannot be resolved
// (e.g. the media selectors of upstream Buildings.Fluid: classes
// aliased by short classes get no modelica-json output). Drop the
// choice instead of emitting a value-less choice modification that
// initializeReplaceable would reject as malformed.
console.log(
`Unable to find choice type: ${basePath}\t${typeSpecifier}`,
);
return;
}
value = replaceableType.modelicaPath; // modelicaPath is the replaceable type
// get selection mods
const classModification = (
const rawModification =
"component_clause1" in replaceable
? (clause as mj.ComponentClause1).component_declaration1.declaration
?.modification
: (clause as mj.ShortClassDefinition).short_class_specifier.value
?.class_modification
?.class_modification;
// Component redeclarations carry a ClassMod object while short class
// redeclarations carry the bare class_modification array — normalize
// so getModificationList always receives a ClassMod.
const classModification = (
Array.isArray(rawModification)
? { class_modification: rawModification }
: rawModification
) as mj.ClassMod;
// Additional modifiers can be attached to choice
if (classModification) {
// Additional modifiers can be attached to choice (a direct binding —
// redeclare X id = value — carries none)
if (classModification?.class_modification) {
// Extract the actual component name from the redeclaration
// (e.g., "selectable_component" from "redeclare ... selectable_component(...)")
const componentName =
Expand Down
Loading