@halleyassist/rule-parser 1.0.26 → 1.0.27
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-parser.browser.js +112 -10
- package/package.json +1 -1
- package/src/errors/ErrorAnalyzer.js +112 -4
- package/src/errors/RuleParseError.js +0 -6
|
@@ -2752,10 +2752,14 @@ class ErrorAnalyzer {
|
|
|
2752
2752
|
|
|
2753
2753
|
// Get snippet (last 50 chars or the whole input if shorter)
|
|
2754
2754
|
const snippetStart = Math.max(0, trimmedInput.length - 50);
|
|
2755
|
-
const
|
|
2755
|
+
const snippetEnd = trimmedInput.length;
|
|
2756
|
+
let snippet = this._buildSnippet(trimmedInput, snippetStart, snippetEnd);
|
|
2756
2757
|
|
|
2757
2758
|
// Analyze the error pattern
|
|
2758
2759
|
const errorInfo = this._detectErrorPattern(trimmedInput, position, snippet);
|
|
2760
|
+
this._enrichBadFunctionCallError(trimmedInput, position, errorInfo, (value) => {
|
|
2761
|
+
snippet = value;
|
|
2762
|
+
}, snippetStart, snippetEnd);
|
|
2759
2763
|
|
|
2760
2764
|
return new RuleParseError(
|
|
2761
2765
|
errorInfo.code,
|
|
@@ -2781,12 +2785,13 @@ class ErrorAnalyzer {
|
|
|
2781
2785
|
// Get snippet around error position
|
|
2782
2786
|
const snippetStart = Math.max(0, position.offset - 20);
|
|
2783
2787
|
const snippetEnd = Math.min(input.length, position.offset + 30);
|
|
2784
|
-
|
|
2785
|
-
input.substring(snippetStart, snippetEnd) +
|
|
2786
|
-
(snippetEnd < input.length ? '...' : '');
|
|
2788
|
+
let snippet = this._buildSnippet(input, snippetStart, snippetEnd);
|
|
2787
2789
|
|
|
2788
2790
|
// Analyze what was expected to determine error type using failureTree
|
|
2789
2791
|
const errorInfo = this._detectErrorFromFailureTree(input, position, expected, found, failureTree);
|
|
2792
|
+
this._enrichBadFunctionCallError(input, position, errorInfo, (value) => {
|
|
2793
|
+
snippet = value;
|
|
2794
|
+
}, snippetStart, snippetEnd);
|
|
2790
2795
|
|
|
2791
2796
|
return new RuleParseError(
|
|
2792
2797
|
errorInfo.code,
|
|
@@ -3300,6 +3305,109 @@ class ErrorAnalyzer {
|
|
|
3300
3305
|
};
|
|
3301
3306
|
}
|
|
3302
3307
|
|
|
3308
|
+
static _enrichBadFunctionCallError(input, position, errorInfo, setSnippet, snippetStart, snippetEnd) {
|
|
3309
|
+
if (!errorInfo || errorInfo.code !== "BAD_FUNCTION_CALL") {
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
const functionContext = this._findFunctionContext(input, position.offset);
|
|
3314
|
+
errorInfo.hint = this._getBadFunctionCallHint(input, position, functionContext);
|
|
3315
|
+
|
|
3316
|
+
if (!functionContext) {
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
|
|
3320
|
+
errorInfo.message = `Invalid function call syntax for ${functionContext.name}.`;
|
|
3321
|
+
setSnippet(this._buildBadFunctionCallSnippet(input, functionContext, snippetStart, snippetEnd));
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
static _buildSnippet(input, start, end) {
|
|
3325
|
+
return (start > 0 ? '...' : '') +
|
|
3326
|
+
input.substring(start, end) +
|
|
3327
|
+
(end < input.length ? '...' : '');
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
static _getBadFunctionCallHint(input, position, functionContext) {
|
|
3331
|
+
const genericHint = "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).";
|
|
3332
|
+
|
|
3333
|
+
if (!functionContext || !this._isArgumentRelatedFunctionCallError(input, position, functionContext)) {
|
|
3334
|
+
return genericHint;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
return "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2). Please check function arguments for invalid syntax.";
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
static _isArgumentRelatedFunctionCallError(input, position, functionContext) {
|
|
3341
|
+
const endOffset = Math.max(functionContext.openParenIndex + 1, Math.min(position.offset, input.length));
|
|
3342
|
+
const argumentText = input.substring(functionContext.openParenIndex + 1, endOffset).trim();
|
|
3343
|
+
return argumentText.length > 0;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
static _buildBadFunctionCallSnippet(input, functionContext, snippetStart, snippetEnd) {
|
|
3347
|
+
if (snippetStart <= functionContext.start) {
|
|
3348
|
+
return this._buildSnippet(input, snippetStart, snippetEnd);
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
const functionPrefix = input.substring(functionContext.start, functionContext.openParenIndex + 1);
|
|
3352
|
+
const suffix = input.substring(snippetStart, snippetEnd);
|
|
3353
|
+
return functionPrefix + '...' + suffix + (snippetEnd < input.length ? '...' : '');
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
static _findFunctionContext(input, offset) {
|
|
3357
|
+
const scanLimit = Math.max(0, Math.min(typeof offset === 'number' ? offset : input.length, input.length));
|
|
3358
|
+
const stack = [];
|
|
3359
|
+
let inString = false;
|
|
3360
|
+
|
|
3361
|
+
for (let i = 0; i < scanLimit; i++) {
|
|
3362
|
+
const char = input[i];
|
|
3363
|
+
|
|
3364
|
+
if (char === '"') {
|
|
3365
|
+
if (!inString) {
|
|
3366
|
+
inString = true;
|
|
3367
|
+
} else {
|
|
3368
|
+
let backslashCount = 0;
|
|
3369
|
+
let j = i - 1;
|
|
3370
|
+
while (j >= 0 && input[j] === '\\') {
|
|
3371
|
+
backslashCount++;
|
|
3372
|
+
j--;
|
|
3373
|
+
}
|
|
3374
|
+
if (backslashCount % 2 === 0) {
|
|
3375
|
+
inString = false;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
continue;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
if (inString) {
|
|
3382
|
+
continue;
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
if (char === '(') {
|
|
3386
|
+
const prefix = input.substring(0, i);
|
|
3387
|
+
const match = /([a-zA-Z_][a-zA-Z0-9_]*)\s*$/.exec(prefix);
|
|
3388
|
+
if (match) {
|
|
3389
|
+
stack.push({
|
|
3390
|
+
name: match[1],
|
|
3391
|
+
start: i - match[1].length,
|
|
3392
|
+
openParenIndex: i
|
|
3393
|
+
});
|
|
3394
|
+
} else {
|
|
3395
|
+
stack.push(null);
|
|
3396
|
+
}
|
|
3397
|
+
} else if (char === ')' && stack.length > 0) {
|
|
3398
|
+
stack.pop();
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
3403
|
+
if (stack[i]) {
|
|
3404
|
+
return stack[i];
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
return null;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3303
3411
|
/**
|
|
3304
3412
|
* Check if string is properly terminated
|
|
3305
3413
|
* @private
|
|
@@ -3671,12 +3779,6 @@ class RuleParseError extends Error {
|
|
|
3671
3779
|
if (this.hint) {
|
|
3672
3780
|
msg += ` Hint: ${this.hint}\n`;
|
|
3673
3781
|
}
|
|
3674
|
-
if (this.found) {
|
|
3675
|
-
msg += ` Found: ${this.found}\n`;
|
|
3676
|
-
}
|
|
3677
|
-
if (this.expected && this.expected.length) {
|
|
3678
|
-
msg += ` Expected: ${this.expected.join(', ')}`;
|
|
3679
|
-
}
|
|
3680
3782
|
return msg;
|
|
3681
3783
|
}
|
|
3682
3784
|
|
package/package.json
CHANGED
|
@@ -44,10 +44,14 @@ class ErrorAnalyzer {
|
|
|
44
44
|
|
|
45
45
|
// Get snippet (last 50 chars or the whole input if shorter)
|
|
46
46
|
const snippetStart = Math.max(0, trimmedInput.length - 50);
|
|
47
|
-
const
|
|
47
|
+
const snippetEnd = trimmedInput.length;
|
|
48
|
+
let snippet = this._buildSnippet(trimmedInput, snippetStart, snippetEnd);
|
|
48
49
|
|
|
49
50
|
// Analyze the error pattern
|
|
50
51
|
const errorInfo = this._detectErrorPattern(trimmedInput, position, snippet);
|
|
52
|
+
this._enrichBadFunctionCallError(trimmedInput, position, errorInfo, (value) => {
|
|
53
|
+
snippet = value;
|
|
54
|
+
}, snippetStart, snippetEnd);
|
|
51
55
|
|
|
52
56
|
return new RuleParseError(
|
|
53
57
|
errorInfo.code,
|
|
@@ -73,12 +77,13 @@ class ErrorAnalyzer {
|
|
|
73
77
|
// Get snippet around error position
|
|
74
78
|
const snippetStart = Math.max(0, position.offset - 20);
|
|
75
79
|
const snippetEnd = Math.min(input.length, position.offset + 30);
|
|
76
|
-
|
|
77
|
-
input.substring(snippetStart, snippetEnd) +
|
|
78
|
-
(snippetEnd < input.length ? '...' : '');
|
|
80
|
+
let snippet = this._buildSnippet(input, snippetStart, snippetEnd);
|
|
79
81
|
|
|
80
82
|
// Analyze what was expected to determine error type using failureTree
|
|
81
83
|
const errorInfo = this._detectErrorFromFailureTree(input, position, expected, found, failureTree);
|
|
84
|
+
this._enrichBadFunctionCallError(input, position, errorInfo, (value) => {
|
|
85
|
+
snippet = value;
|
|
86
|
+
}, snippetStart, snippetEnd);
|
|
82
87
|
|
|
83
88
|
return new RuleParseError(
|
|
84
89
|
errorInfo.code,
|
|
@@ -592,6 +597,109 @@ class ErrorAnalyzer {
|
|
|
592
597
|
};
|
|
593
598
|
}
|
|
594
599
|
|
|
600
|
+
static _enrichBadFunctionCallError(input, position, errorInfo, setSnippet, snippetStart, snippetEnd) {
|
|
601
|
+
if (!errorInfo || errorInfo.code !== "BAD_FUNCTION_CALL") {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const functionContext = this._findFunctionContext(input, position.offset);
|
|
606
|
+
errorInfo.hint = this._getBadFunctionCallHint(input, position, functionContext);
|
|
607
|
+
|
|
608
|
+
if (!functionContext) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
errorInfo.message = `Invalid function call syntax for ${functionContext.name}.`;
|
|
613
|
+
setSnippet(this._buildBadFunctionCallSnippet(input, functionContext, snippetStart, snippetEnd));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
static _buildSnippet(input, start, end) {
|
|
617
|
+
return (start > 0 ? '...' : '') +
|
|
618
|
+
input.substring(start, end) +
|
|
619
|
+
(end < input.length ? '...' : '');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
static _getBadFunctionCallHint(input, position, functionContext) {
|
|
623
|
+
const genericHint = "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).";
|
|
624
|
+
|
|
625
|
+
if (!functionContext || !this._isArgumentRelatedFunctionCallError(input, position, functionContext)) {
|
|
626
|
+
return genericHint;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2). Please check function arguments for invalid syntax.";
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
static _isArgumentRelatedFunctionCallError(input, position, functionContext) {
|
|
633
|
+
const endOffset = Math.max(functionContext.openParenIndex + 1, Math.min(position.offset, input.length));
|
|
634
|
+
const argumentText = input.substring(functionContext.openParenIndex + 1, endOffset).trim();
|
|
635
|
+
return argumentText.length > 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
static _buildBadFunctionCallSnippet(input, functionContext, snippetStart, snippetEnd) {
|
|
639
|
+
if (snippetStart <= functionContext.start) {
|
|
640
|
+
return this._buildSnippet(input, snippetStart, snippetEnd);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const functionPrefix = input.substring(functionContext.start, functionContext.openParenIndex + 1);
|
|
644
|
+
const suffix = input.substring(snippetStart, snippetEnd);
|
|
645
|
+
return functionPrefix + '...' + suffix + (snippetEnd < input.length ? '...' : '');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
static _findFunctionContext(input, offset) {
|
|
649
|
+
const scanLimit = Math.max(0, Math.min(typeof offset === 'number' ? offset : input.length, input.length));
|
|
650
|
+
const stack = [];
|
|
651
|
+
let inString = false;
|
|
652
|
+
|
|
653
|
+
for (let i = 0; i < scanLimit; i++) {
|
|
654
|
+
const char = input[i];
|
|
655
|
+
|
|
656
|
+
if (char === '"') {
|
|
657
|
+
if (!inString) {
|
|
658
|
+
inString = true;
|
|
659
|
+
} else {
|
|
660
|
+
let backslashCount = 0;
|
|
661
|
+
let j = i - 1;
|
|
662
|
+
while (j >= 0 && input[j] === '\\') {
|
|
663
|
+
backslashCount++;
|
|
664
|
+
j--;
|
|
665
|
+
}
|
|
666
|
+
if (backslashCount % 2 === 0) {
|
|
667
|
+
inString = false;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (inString) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (char === '(') {
|
|
678
|
+
const prefix = input.substring(0, i);
|
|
679
|
+
const match = /([a-zA-Z_][a-zA-Z0-9_]*)\s*$/.exec(prefix);
|
|
680
|
+
if (match) {
|
|
681
|
+
stack.push({
|
|
682
|
+
name: match[1],
|
|
683
|
+
start: i - match[1].length,
|
|
684
|
+
openParenIndex: i
|
|
685
|
+
});
|
|
686
|
+
} else {
|
|
687
|
+
stack.push(null);
|
|
688
|
+
}
|
|
689
|
+
} else if (char === ')' && stack.length > 0) {
|
|
690
|
+
stack.pop();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
695
|
+
if (stack[i]) {
|
|
696
|
+
return stack[i];
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
|
|
595
703
|
/**
|
|
596
704
|
* Check if string is properly terminated
|
|
597
705
|
* @private
|
|
@@ -37,12 +37,6 @@ class RuleParseError extends Error {
|
|
|
37
37
|
if (this.hint) {
|
|
38
38
|
msg += ` Hint: ${this.hint}\n`;
|
|
39
39
|
}
|
|
40
|
-
if (this.found) {
|
|
41
|
-
msg += ` Found: ${this.found}\n`;
|
|
42
|
-
}
|
|
43
|
-
if (this.expected && this.expected.length) {
|
|
44
|
-
msg += ` Expected: ${this.expected.join(', ')}`;
|
|
45
|
-
}
|
|
46
40
|
return msg;
|
|
47
41
|
}
|
|
48
42
|
|