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
50 changes: 49 additions & 1 deletion src/TemplateMarkInterpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,13 +575,61 @@ export class TemplateMarkInterpreter {
const serializer = new Serializer(factory, modelManager);
try {
serializer.fromJSON(templateMark);
return templateMark;
}
catch (err) {
throw new Error(`Generated invalid agreement: ${err}: ${JSON.stringify(templateMark, null, 2)}`);
}

const errors: Array<{ propertyName: string, message: string }> = [];
const templateClass = this.templateClass;
const guardBlockPaths: Map<string, string> = new Map();

traverse(templateMark).forEach(function (node: any) {
if (!node || typeof node !== 'object' || !node.$class) return;

const currentPath = this.path.join('/');
if (OPTIONAL_DEFINITION_RE.test(node.$class) ||
CONDITIONAL_DEFINITION_RE.test(node.$class) ||
WITH_DEFINITION_RE.test(node.$class)) {
guardBlockPaths.set(node.name, currentPath);
}
if (VARIABLE_DEFINITION_RE.test(node.$class) ||
ENUM_VARIABLE_DEFINITION_RE.test(node.$class) ||
FORMATTED_VARIABLE_DEFINITION_RE.test(node.$class)) {
const propName = node.name;
if (propName && propName !== 'this') {
try {
const property = templateClass.getProperty(propName);
if (property && property.isOptional()) {
const guardPath = guardBlockPaths.get(propName);
const isGuarded = guardPath !== undefined &&
(currentPath === guardPath || currentPath.startsWith(guardPath + '/'));
if (!isGuarded) {
errors.push({
propertyName: propName,
message: `Optional property '${propName}' is used without a guard. Wrap it in {{#optional ${propName}}}...{{/optional}} or {{#if ${propName}}}...{{/if}}.`
});
}
}
} catch {
// Property not found at root level, might be nested - skip
}
}
}
});

if (errors.length > 0) {
const errorMessage = `Optional properties used without guards: ${errors.map(e => e.propertyName).join(', ')}`;
const error = new Error(errorMessage);
(error as any).errors = errors;
throw error;
}

return templateMark;
}



/**
* Compiles the code nodes containing TS to code nodes containing JS.
* @param {*} templateMark the TemplateMark JSON object
Expand Down
23 changes: 3 additions & 20 deletions test/__snapshots__/TemplateMarkInterpreter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,9 @@ exports[`templatemark interpreter should fail to generate formula-no-method 1`]
]
`;

exports[`templatemark interpreter should fail to generate formula-optional-noguard 1`] = `
[
{
"code": " return message.length ",
"errors": [
{
"category": 1,
"character": 10,
"code": 18048,
"id": "err-18048-475-7",
"length": 7,
"line": 82,
"renderedMessage": "'message' is possibly 'undefined'.",
"start": 1793,
},
],
"nodeId": "formula_a3e0e90a2ad3601e1b208a0581e1ce6dfc5aab302d20cb2a0488d78e236096f7",
},
]
`;
exports[`templatemark interpreter should fail to generate formula-optional-noguard 1`] = `[Error: Optional properties used without guards: message]`;

exports[`templatemark interpreter should fail to generate optional-noguard 1`] = `[Error: Optional properties used without guards: nickname]`;

exports[`templatemark interpreter should fail to generate template-missing-field 1`] = `[TemplateException: Unknown property: missing File text/grammar.tem.md line -1 column -1]`;

Expand Down
4 changes: 4 additions & 0 deletions test/templates/bad/optional-noguard/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$class": "test@1.0.0.TemplateData",
"name": "World"
}
7 changes: 7 additions & 0 deletions test/templates/bad/optional-noguard/model.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace test@1.0.0

@template
concept TemplateData {
o String name
o String nickname optional
}
1 change: 1 addition & 0 deletions test/templates/bad/optional-noguard/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello {{name}}! Your nickname is {{nickname}}.
2 changes: 1 addition & 1 deletion test/templates/good/playground/model.cto
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ concept Order {
concept TemplateData {
o String name
o Address address
o Integer age optional
o Integer age
o MonetaryAmount salary
o String[] favoriteColors
o Order order
Expand Down