@halleyassist/rule-templater 0.0.16 → 0.0.17
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/dist/rule-templater.browser.js +44 -4
- package/index.d.ts +25 -1
- package/index.js +2 -0
- package/package.json +1 -1
- package/src/HalleyFunctionBlob.js +126 -0
- package/src/RuleTemplate.js +44 -4
- package/src/RuleTemplate.production.js +44 -4
|
@@ -3918,17 +3918,20 @@ class RuleTemplate {
|
|
|
3918
3918
|
/**
|
|
3919
3919
|
* Validate variable types against the AST
|
|
3920
3920
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
3921
|
-
* @
|
|
3921
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
3922
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
3922
3923
|
*/
|
|
3923
|
-
validate(variables) {
|
|
3924
|
+
validate(variables, functionBlob) {
|
|
3924
3925
|
if (!variables || typeof variables !== 'object') {
|
|
3925
3926
|
return {
|
|
3926
3927
|
valid: false,
|
|
3927
|
-
errors: ['Variables must be provided as an object']
|
|
3928
|
+
errors: ['Variables must be provided as an object'],
|
|
3929
|
+
warnings: []
|
|
3928
3930
|
};
|
|
3929
3931
|
}
|
|
3930
3932
|
|
|
3931
3933
|
const errors = [];
|
|
3934
|
+
const warnings = [];
|
|
3932
3935
|
const extractedVars = this.extractVariables();
|
|
3933
3936
|
|
|
3934
3937
|
for (const varInfo of extractedVars) {
|
|
@@ -3961,13 +3964,50 @@ class RuleTemplate {
|
|
|
3961
3964
|
}
|
|
3962
3965
|
}
|
|
3963
3966
|
}
|
|
3967
|
+
|
|
3968
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
3969
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
3970
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3964
3973
|
|
|
3965
3974
|
return {
|
|
3966
3975
|
valid: errors.length === 0,
|
|
3967
|
-
errors
|
|
3976
|
+
errors,
|
|
3977
|
+
warnings
|
|
3968
3978
|
};
|
|
3969
3979
|
}
|
|
3970
3980
|
|
|
3981
|
+
_extractFunctionCalls() {
|
|
3982
|
+
const functionCalls = [];
|
|
3983
|
+
|
|
3984
|
+
const traverse = (node) => {
|
|
3985
|
+
if (!node) return;
|
|
3986
|
+
|
|
3987
|
+
if (node.type === 'fcall') {
|
|
3988
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
3989
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
3990
|
+
if (functionName) {
|
|
3991
|
+
functionCalls.push({
|
|
3992
|
+
name: functionName,
|
|
3993
|
+
arguments: argumentsNode?.children
|
|
3994
|
+
?.filter(c => c.type === 'argument')
|
|
3995
|
+
.map(c => c.text) || []
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
if (node.children) {
|
|
4001
|
+
for (const child of node.children) {
|
|
4002
|
+
traverse(child);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
4006
|
+
|
|
4007
|
+
traverse(this.ast);
|
|
4008
|
+
return functionCalls;
|
|
4009
|
+
}
|
|
4010
|
+
|
|
3971
4011
|
/**
|
|
3972
4012
|
* Prepare the template by replacing variables with their values
|
|
3973
4013
|
* Rebuilds from AST by iterating through children
|
package/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface Variables {
|
|
|
26
26
|
export interface ValidationResult {
|
|
27
27
|
valid: boolean;
|
|
28
28
|
errors: string[];
|
|
29
|
+
warnings: string[];
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export interface VariableValidationResult {
|
|
@@ -60,6 +61,17 @@ export interface TemplateFiltersType {
|
|
|
60
61
|
[key: string]: FilterFunction;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
export interface HalleyFunctionDefinition {
|
|
65
|
+
name: string;
|
|
66
|
+
arguments: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface HalleyFunctionBlobData {
|
|
70
|
+
_schema?: number;
|
|
71
|
+
version?: string;
|
|
72
|
+
functions?: HalleyFunctionDefinition[];
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
export class RuleTemplate {
|
|
64
76
|
ruleTemplateText: string;
|
|
65
77
|
ast: ASTNode;
|
|
@@ -90,7 +102,7 @@ export class RuleTemplate {
|
|
|
90
102
|
* @param variables Object mapping variable names to {value, type} objects
|
|
91
103
|
* @returns Object with validation results: {valid, errors}
|
|
92
104
|
*/
|
|
93
|
-
validate(variables: Variables): ValidationResult;
|
|
105
|
+
validate(variables: Variables, functionBlob?: HalleyFunctionBlob): ValidationResult;
|
|
94
106
|
|
|
95
107
|
/**
|
|
96
108
|
* Prepare the template by replacing variables with their values
|
|
@@ -125,6 +137,18 @@ export class GeneralTemplate {
|
|
|
125
137
|
prepare(variables: Variables): string;
|
|
126
138
|
}
|
|
127
139
|
|
|
140
|
+
export class HalleyFunctionBlob {
|
|
141
|
+
_schema?: number;
|
|
142
|
+
version?: string;
|
|
143
|
+
functions: HalleyFunctionDefinition[];
|
|
144
|
+
|
|
145
|
+
constructor(jsonData: HalleyFunctionBlobData);
|
|
146
|
+
|
|
147
|
+
static fromURL(url: string): Promise<HalleyFunctionBlob>;
|
|
148
|
+
|
|
149
|
+
validate(functionName: string, variables?: any[]): string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
export class VariableTemplate {
|
|
129
153
|
templateText: string;
|
|
130
154
|
ast: ASTNode;
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const RuleTemplate = require('./src/RuleTemplate');
|
|
2
2
|
const GeneralTemplate = require('./src/GeneralTemplate');
|
|
3
|
+
const HalleyFunctionBlob = require('./src/HalleyFunctionBlob');
|
|
3
4
|
const VariableTemplate = require('./src/VariableTemplate');
|
|
4
5
|
const VariableValidate = require('./src/VariableValidate');
|
|
5
6
|
|
|
@@ -9,4 +10,5 @@ module.exports.VariableTypes = RuleTemplate.VariableTypes;
|
|
|
9
10
|
module.exports.TemplateFilters = RuleTemplate.TemplateFilters;
|
|
10
11
|
module.exports.VariableValidate = VariableValidate;
|
|
11
12
|
module.exports.GeneralTemplate = GeneralTemplate;
|
|
13
|
+
module.exports.HalleyFunctionBlob = HalleyFunctionBlob;
|
|
12
14
|
module.exports.VariableTemplate = VariableTemplate;
|
package/package.json
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
class HalleyFunctionBlob {
|
|
2
|
+
constructor(jsonData) {
|
|
3
|
+
const blobData = jsonData && typeof jsonData === 'object' ? jsonData : {};
|
|
4
|
+
|
|
5
|
+
this._schema = blobData._schema;
|
|
6
|
+
this.version = blobData.version;
|
|
7
|
+
this.functions = [];
|
|
8
|
+
this.functionMap = new Map();
|
|
9
|
+
|
|
10
|
+
const functions = Array.isArray(blobData.functions) ? blobData.functions : [];
|
|
11
|
+
for (const definition of functions) {
|
|
12
|
+
if (!definition || typeof definition.name !== 'string') {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const normalizedDefinition = {
|
|
17
|
+
name: definition.name,
|
|
18
|
+
arguments: Array.isArray(definition.arguments) ? definition.arguments.slice() : []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
this.functions.push(normalizedDefinition);
|
|
22
|
+
this.functionMap.set(normalizedDefinition.name, normalizedDefinition);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async fromURL(url) {
|
|
27
|
+
if (typeof url !== 'string' || !url.trim()) {
|
|
28
|
+
throw new Error('A function blob URL must be provided');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof globalThis.fetch !== 'function') {
|
|
32
|
+
throw new Error('Fetch API is not available');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const response = await globalThis.fetch(url);
|
|
36
|
+
if (!response || !response.ok) {
|
|
37
|
+
throw new Error(`Failed to fetch function blob from '${url}'`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new HalleyFunctionBlob(await response.json());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validate(functionName, variables = []) {
|
|
44
|
+
const warnings = [];
|
|
45
|
+
const functionDefinition = this.functionMap.get(functionName);
|
|
46
|
+
|
|
47
|
+
if (!functionDefinition) {
|
|
48
|
+
return [`function '${functionName}' does not exist`];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const providedVariables = Array.isArray(variables) ? variables : [];
|
|
52
|
+
const providedCount = providedVariables.length;
|
|
53
|
+
const parameterRange = this._getParameterRange(functionDefinition.arguments);
|
|
54
|
+
|
|
55
|
+
for (let idx = 0; idx < functionDefinition.arguments.length; idx++) {
|
|
56
|
+
const argumentName = functionDefinition.arguments[idx];
|
|
57
|
+
if (argumentName === '...') {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (providedCount > idx || argumentName.endsWith('?')) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
warnings.push(
|
|
66
|
+
`parameter ${idx + 1} of ${functionName} '${argumentName}' is missing, function expects ${parameterRange}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this._hasTooManyArguments(functionDefinition.arguments, providedCount)) {
|
|
71
|
+
warnings.push(
|
|
72
|
+
`${functionName} received ${providedCount} parameters, function expects ${parameterRange}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return warnings;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_hasTooManyArguments(argumentList, providedCount) {
|
|
80
|
+
const { max } = this._getArgumentBounds(argumentList);
|
|
81
|
+
return max !== Infinity && providedCount > max;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
_getParameterRange(argumentList) {
|
|
85
|
+
const { min, max } = this._getArgumentBounds(argumentList);
|
|
86
|
+
|
|
87
|
+
if (max === Infinity) {
|
|
88
|
+
if (min === 0) {
|
|
89
|
+
return 'any number of parameters';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `at least ${min} ${min === 1 ? 'parameter' : 'parameters'}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (min === max) {
|
|
96
|
+
return `${min} ${min === 1 ? 'parameter' : 'parameters'}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return `${min} to ${max} parameters`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_getArgumentBounds(argumentList) {
|
|
103
|
+
let min = 0;
|
|
104
|
+
let max = 0;
|
|
105
|
+
let variadic = false;
|
|
106
|
+
|
|
107
|
+
for (const argumentName of argumentList) {
|
|
108
|
+
if (argumentName === '...') {
|
|
109
|
+
variadic = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
max++;
|
|
114
|
+
if (!argumentName.endsWith('?')) {
|
|
115
|
+
min++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
min,
|
|
121
|
+
max: variadic ? Infinity : max
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = HalleyFunctionBlob;
|
package/src/RuleTemplate.js
CHANGED
|
@@ -215,17 +215,20 @@ class RuleTemplate {
|
|
|
215
215
|
/**
|
|
216
216
|
* Validate variable types against the AST
|
|
217
217
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
218
|
-
* @
|
|
218
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
219
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
219
220
|
*/
|
|
220
|
-
validate(variables) {
|
|
221
|
+
validate(variables, functionBlob) {
|
|
221
222
|
if (!variables || typeof variables !== 'object') {
|
|
222
223
|
return {
|
|
223
224
|
valid: false,
|
|
224
|
-
errors: ['Variables must be provided as an object']
|
|
225
|
+
errors: ['Variables must be provided as an object'],
|
|
226
|
+
warnings: []
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
const errors = [];
|
|
231
|
+
const warnings = [];
|
|
229
232
|
const extractedVars = this.extractVariables();
|
|
230
233
|
|
|
231
234
|
for (const varInfo of extractedVars) {
|
|
@@ -258,13 +261,50 @@ class RuleTemplate {
|
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
|
|
265
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
266
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
267
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
261
270
|
|
|
262
271
|
return {
|
|
263
272
|
valid: errors.length === 0,
|
|
264
|
-
errors
|
|
273
|
+
errors,
|
|
274
|
+
warnings
|
|
265
275
|
};
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
_extractFunctionCalls() {
|
|
279
|
+
const functionCalls = [];
|
|
280
|
+
|
|
281
|
+
const traverse = (node) => {
|
|
282
|
+
if (!node) return;
|
|
283
|
+
|
|
284
|
+
if (node.type === 'fcall') {
|
|
285
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
286
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
287
|
+
if (functionName) {
|
|
288
|
+
functionCalls.push({
|
|
289
|
+
name: functionName,
|
|
290
|
+
arguments: argumentsNode?.children
|
|
291
|
+
?.filter(c => c.type === 'argument')
|
|
292
|
+
.map(c => c.text) || []
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (node.children) {
|
|
298
|
+
for (const child of node.children) {
|
|
299
|
+
traverse(child);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
traverse(this.ast);
|
|
305
|
+
return functionCalls;
|
|
306
|
+
}
|
|
307
|
+
|
|
268
308
|
/**
|
|
269
309
|
* Prepare the template by replacing variables with their values
|
|
270
310
|
* Rebuilds from AST by iterating through children
|
|
@@ -215,17 +215,20 @@ class RuleTemplate {
|
|
|
215
215
|
/**
|
|
216
216
|
* Validate variable types against the AST
|
|
217
217
|
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
218
|
-
* @
|
|
218
|
+
* @param {Object} [functionBlob] - Optional HalleyFunctionBlob used for non-fatal function warnings
|
|
219
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: [], warnings: []}
|
|
219
220
|
*/
|
|
220
|
-
validate(variables) {
|
|
221
|
+
validate(variables, functionBlob) {
|
|
221
222
|
if (!variables || typeof variables !== 'object') {
|
|
222
223
|
return {
|
|
223
224
|
valid: false,
|
|
224
|
-
errors: ['Variables must be provided as an object']
|
|
225
|
+
errors: ['Variables must be provided as an object'],
|
|
226
|
+
warnings: []
|
|
225
227
|
};
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
const errors = [];
|
|
231
|
+
const warnings = [];
|
|
229
232
|
const extractedVars = this.extractVariables();
|
|
230
233
|
|
|
231
234
|
for (const varInfo of extractedVars) {
|
|
@@ -258,13 +261,50 @@ class RuleTemplate {
|
|
|
258
261
|
}
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
|
|
265
|
+
if (functionBlob && typeof functionBlob.validate === 'function') {
|
|
266
|
+
for (const functionCall of this._extractFunctionCalls()) {
|
|
267
|
+
warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
261
270
|
|
|
262
271
|
return {
|
|
263
272
|
valid: errors.length === 0,
|
|
264
|
-
errors
|
|
273
|
+
errors,
|
|
274
|
+
warnings
|
|
265
275
|
};
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
_extractFunctionCalls() {
|
|
279
|
+
const functionCalls = [];
|
|
280
|
+
|
|
281
|
+
const traverse = (node) => {
|
|
282
|
+
if (!node) return;
|
|
283
|
+
|
|
284
|
+
if (node.type === 'fcall') {
|
|
285
|
+
const functionName = node.children?.find(c => c.type === 'fname')?.text?.trim();
|
|
286
|
+
const argumentsNode = node.children?.find(c => c.type === 'arguments');
|
|
287
|
+
if (functionName) {
|
|
288
|
+
functionCalls.push({
|
|
289
|
+
name: functionName,
|
|
290
|
+
arguments: argumentsNode?.children
|
|
291
|
+
?.filter(c => c.type === 'argument')
|
|
292
|
+
.map(c => c.text) || []
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (node.children) {
|
|
298
|
+
for (const child of node.children) {
|
|
299
|
+
traverse(child);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
traverse(this.ast);
|
|
305
|
+
return functionCalls;
|
|
306
|
+
}
|
|
307
|
+
|
|
268
308
|
/**
|
|
269
309
|
* Prepare the template by replacing variables with their values
|
|
270
310
|
* Rebuilds from AST by iterating through children
|