@malloydata/malloy 0.0.237-dev250225144145 → 0.0.237-dev250225213433
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/lang/syntax-errors/custom-error-messages.d.ts +4 -2
- package/dist/lang/syntax-errors/custom-error-messages.js +75 -16
- package/dist/lang/syntax-errors/malloy-error-strategy.d.ts +4 -6
- package/dist/lang/syntax-errors/malloy-error-strategy.js +3 -22
- package/dist/lang/syntax-errors/malloy-parser-error-listener.d.ts +1 -1
- package/dist/lang/syntax-errors/malloy-parser-error-listener.js +107 -10
- package/dist/lang/test/syntax-errors.spec.js +113 -59
- package/package.json +3 -3
|
@@ -2,10 +2,12 @@ import { Parser, Token } from 'antlr4ts';
|
|
|
2
2
|
export interface ErrorCase {
|
|
3
3
|
ruleContextOptions?: string[];
|
|
4
4
|
offendingSymbol?: number;
|
|
5
|
+
offendingSymbolTextOptions?: string[];
|
|
5
6
|
currentToken?: number;
|
|
6
7
|
precedingTokenOptions?: number[][];
|
|
7
|
-
lookAheadOptions?: number[][];
|
|
8
|
+
lookAheadOptions?: (number | string)[][];
|
|
8
9
|
errorMessage: string;
|
|
9
|
-
|
|
10
|
+
lookbackSiblingRuleOptions?: number[];
|
|
11
|
+
predecessorHasAncestorRule?: number;
|
|
10
12
|
}
|
|
11
13
|
export declare const checkCustomErrorMessage: (parser: Parser, offendingSymbol: Token | undefined, errorCases: ErrorCase[]) => string;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.checkCustomErrorMessage = void 0;
|
|
10
10
|
const checkCustomErrorMessage = (parser, offendingSymbol, errorCases) => {
|
|
11
|
+
var _a, _b;
|
|
11
12
|
const currentRuleName = parser.getRuleInvocationStack()[0];
|
|
12
13
|
const currentToken = parser.currentToken;
|
|
13
14
|
for (const errorCase of errorCases) {
|
|
@@ -17,52 +18,110 @@ const checkCustomErrorMessage = (parser, offendingSymbol, errorCases) => {
|
|
|
17
18
|
(offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.type) === errorCase.offendingSymbol;
|
|
18
19
|
const isRuleContextMatch = !errorCase.ruleContextOptions ||
|
|
19
20
|
errorCase.ruleContextOptions.includes(currentRuleName);
|
|
20
|
-
|
|
21
|
+
const isOffendingSymbolTextMatch = !errorCase.offendingSymbolTextOptions ||
|
|
22
|
+
errorCase.offendingSymbolTextOptions.includes(((_a = offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.text) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '');
|
|
23
|
+
if (isCurrentTokenMatch &&
|
|
24
|
+
isOffendingSymbolMatch &&
|
|
25
|
+
isRuleContextMatch &&
|
|
26
|
+
isOffendingSymbolTextMatch) {
|
|
27
|
+
if (errorCase.lookbackSiblingRuleOptions) {
|
|
28
|
+
const siblings = parser.ruleContext.children;
|
|
29
|
+
if (!siblings) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// We use 'any' here because the sibling isn't guaranteed to be a RuleContext,
|
|
33
|
+
// but we only care if it has a ruleIndex. If it doesn't then .includes() won't
|
|
34
|
+
// match anyways.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
const precedingRule = siblings[siblings.length - 1];
|
|
37
|
+
if (!errorCase.lookbackSiblingRuleOptions.includes(precedingRule.ruleIndex)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
21
41
|
// If so, try to check the preceding tokens.
|
|
22
42
|
if (errorCase.precedingTokenOptions) {
|
|
23
|
-
const hasPrecedingTokenMatch = errorCase.precedingTokenOptions.some(sequence => checkTokenSequenceMatch(parser, sequence, 'lookback',
|
|
24
|
-
? offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.tokenIndex
|
|
25
|
-
: undefined));
|
|
43
|
+
const hasPrecedingTokenMatch = errorCase.precedingTokenOptions.some(sequence => checkTokenSequenceMatch(parser, sequence, 'lookback', offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.tokenIndex));
|
|
26
44
|
if (!hasPrecedingTokenMatch) {
|
|
27
45
|
continue; // Continue to check a different error case
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
48
|
if (errorCase.lookAheadOptions) {
|
|
31
|
-
const hasLookaheadTokenMatch = errorCase.lookAheadOptions.some(sequence => checkTokenSequenceMatch(parser, sequence, 'lookahead',
|
|
32
|
-
? offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.tokenIndex
|
|
33
|
-
: undefined));
|
|
49
|
+
const hasLookaheadTokenMatch = errorCase.lookAheadOptions.some(sequence => checkTokenSequenceMatch(parser, sequence, 'lookahead', offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.tokenIndex));
|
|
34
50
|
if (!hasLookaheadTokenMatch) {
|
|
35
51
|
continue; // Continue to check a different error case
|
|
36
52
|
}
|
|
37
53
|
}
|
|
54
|
+
if (errorCase.predecessorHasAncestorRule) {
|
|
55
|
+
const precedingSibling = (_b = parser.ruleContext.children) === null || _b === void 0 ? void 0 : _b[0];
|
|
56
|
+
if (!precedingSibling ||
|
|
57
|
+
!doesRightmostBranchContainRule(precedingSibling, errorCase.predecessorHasAncestorRule)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
38
61
|
// If all cases match, return the custom error message
|
|
39
|
-
|
|
62
|
+
let message = errorCase.errorMessage
|
|
63
|
+
.replace('${currentToken}', currentToken.text || '')
|
|
64
|
+
.replace('${offendingSymbol}', (offendingSymbol === null || offendingSymbol === void 0 ? void 0 : offendingSymbol.text) || '');
|
|
65
|
+
try {
|
|
66
|
+
const previousToken = parser.inputStream.LT(-1);
|
|
67
|
+
message = message.replace('${previousToken}', previousToken.text || '');
|
|
68
|
+
}
|
|
69
|
+
catch (ex) {
|
|
70
|
+
// This shouldn't ever occur, but if it does, just leave the untokenized message.
|
|
71
|
+
}
|
|
40
72
|
return message;
|
|
41
73
|
}
|
|
42
74
|
}
|
|
43
75
|
return '';
|
|
44
76
|
};
|
|
45
77
|
exports.checkCustomErrorMessage = checkCustomErrorMessage;
|
|
78
|
+
// Recursively walk the rightmost branch (the path to the most recent token)
|
|
79
|
+
// to see whether any of those parse tree nodes match the provided rule number.
|
|
80
|
+
const doesRightmostBranchContainRule = (root, ruleNumber, depthLimit = 20) => {
|
|
81
|
+
var _a;
|
|
82
|
+
if (root.ruleIndex === ruleNumber) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (depthLimit <= 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const childCount = ((_a = root.children) === null || _a === void 0 ? void 0 : _a.length) || 0;
|
|
89
|
+
if (root.children && childCount > 0) {
|
|
90
|
+
const rightmostChild = root.children[root.children.length - 1];
|
|
91
|
+
return doesRightmostBranchContainRule(rightmostChild, ruleNumber, depthLimit - 1);
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
};
|
|
46
95
|
const checkTokenSequenceMatch = (parser, sequence, direction, anchorTokenPosition) => {
|
|
96
|
+
var _a;
|
|
47
97
|
try {
|
|
48
98
|
for (let i = 0; i < sequence.length; i++) {
|
|
49
99
|
let streamToken = undefined;
|
|
50
100
|
if (typeof anchorTokenPosition === 'number') {
|
|
51
101
|
const tokenOffset = direction === 'lookahead' ? i + 1 : -1 * (i + 1);
|
|
52
|
-
streamToken = parser.inputStream.get(anchorTokenPosition + tokenOffset)
|
|
102
|
+
streamToken = parser.inputStream.get(anchorTokenPosition + tokenOffset);
|
|
53
103
|
}
|
|
54
104
|
else {
|
|
55
105
|
// Note: positive lookahead starts at '2' because '1' is the current token.
|
|
56
106
|
const tokenOffset = direction === 'lookahead' ? i + 2 : -1 * (i + 1);
|
|
57
|
-
streamToken = parser.inputStream.
|
|
107
|
+
streamToken = parser.inputStream.LT(tokenOffset);
|
|
58
108
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
109
|
+
if (typeof sequence[i] === 'number') {
|
|
110
|
+
const tokenIndex = sequence[i];
|
|
111
|
+
// Note: negative checking is < -1 becuase Token.EOF is -1, but below
|
|
112
|
+
// that we use negatives to indicate "does-not-match" rules.
|
|
113
|
+
if (tokenIndex >= -1 && streamToken.type !== tokenIndex) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (tokenIndex < -1 && streamToken.type === -1 * tokenIndex) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
63
119
|
}
|
|
64
|
-
if (sequence[i]
|
|
65
|
-
|
|
120
|
+
else if (typeof sequence[i] === 'string') {
|
|
121
|
+
const tokenText = sequence[i];
|
|
122
|
+
if (tokenText !== ((_a = streamToken.text) === null || _a === void 0 ? void 0 : _a.toLowerCase())) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
66
125
|
}
|
|
67
126
|
}
|
|
68
127
|
return true;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { DefaultErrorStrategy
|
|
1
|
+
import { DefaultErrorStrategy } from 'antlr4ts';
|
|
2
2
|
/**
|
|
3
|
-
* Custom error strategy for the Malloy Parser.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* custom error strategy will fall back to the default error strategy.
|
|
3
|
+
* Custom error strategy for the Malloy Parser.
|
|
4
|
+
*
|
|
5
|
+
* This class does not currently override default ANTLR error handling.
|
|
7
6
|
*
|
|
8
7
|
* For more details, read the documentation in DefaultErrorStrategy.d.ts
|
|
9
8
|
* or reference the superclass at:
|
|
10
9
|
* https://github.com/tunnelvisionlabs/antlr4ts/blob/master/src/DefaultErrorStrategy.ts
|
|
11
10
|
*/
|
|
12
11
|
export declare class MalloyErrorStrategy extends DefaultErrorStrategy {
|
|
13
|
-
sync(parser: Parser): void;
|
|
14
12
|
}
|
|
@@ -8,35 +8,16 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.MalloyErrorStrategy = void 0;
|
|
10
10
|
const antlr4ts_1 = require("antlr4ts");
|
|
11
|
-
const MalloyParser_1 = require("../lib/Malloy/MalloyParser");
|
|
12
|
-
const custom_error_messages_1 = require("./custom-error-messages");
|
|
13
|
-
const customErrorCases = [
|
|
14
|
-
{
|
|
15
|
-
errorMessage: "Missing '{' after 'extend'",
|
|
16
|
-
currentToken: MalloyParser_1.MalloyParser.EXTEND,
|
|
17
|
-
ruleContextOptions: ['sqExpr'],
|
|
18
|
-
lookAheadOptions: [[-MalloyParser_1.MalloyParser.OCURLY]],
|
|
19
|
-
},
|
|
20
|
-
];
|
|
21
11
|
/**
|
|
22
|
-
* Custom error strategy for the Malloy Parser.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* custom error strategy will fall back to the default error strategy.
|
|
12
|
+
* Custom error strategy for the Malloy Parser.
|
|
13
|
+
*
|
|
14
|
+
* This class does not currently override default ANTLR error handling.
|
|
26
15
|
*
|
|
27
16
|
* For more details, read the documentation in DefaultErrorStrategy.d.ts
|
|
28
17
|
* or reference the superclass at:
|
|
29
18
|
* https://github.com/tunnelvisionlabs/antlr4ts/blob/master/src/DefaultErrorStrategy.ts
|
|
30
19
|
*/
|
|
31
20
|
class MalloyErrorStrategy extends antlr4ts_1.DefaultErrorStrategy {
|
|
32
|
-
sync(parser) {
|
|
33
|
-
const interceptedErrorMessage = (0, custom_error_messages_1.checkCustomErrorMessage)(parser, undefined, customErrorCases);
|
|
34
|
-
if (interceptedErrorMessage) {
|
|
35
|
-
parser.notifyErrorListeners(interceptedErrorMessage, parser.currentToken, undefined);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
super.sync(parser);
|
|
39
|
-
}
|
|
40
21
|
}
|
|
41
22
|
exports.MalloyErrorStrategy = MalloyErrorStrategy;
|
|
42
23
|
//# sourceMappingURL=malloy-error-strategy.js.map
|
|
@@ -2,7 +2,7 @@ import { ANTLRErrorListener, Token } from 'antlr4ts';
|
|
|
2
2
|
import { ErrorCase } from './custom-error-messages';
|
|
3
3
|
import { MessageLogger, MessageCode, MessageParameterType, LogMessageOptions } from '../parse-log';
|
|
4
4
|
import { MalloyTranslation } from '../parse-malloy';
|
|
5
|
-
export declare const
|
|
5
|
+
export declare const malloyCustomErrorCases: ErrorCase[];
|
|
6
6
|
export declare class MalloyParserErrorListener implements ANTLRErrorListener<Token> {
|
|
7
7
|
readonly translator: MalloyTranslation;
|
|
8
8
|
readonly messages: MessageLogger;
|
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.MalloyParserErrorListener = exports.
|
|
9
|
+
exports.MalloyParserErrorListener = exports.malloyCustomErrorCases = void 0;
|
|
10
10
|
const MalloyParser_1 = require("../lib/Malloy/MalloyParser");
|
|
11
11
|
const custom_error_messages_1 = require("./custom-error-messages");
|
|
12
12
|
const parse_log_1 = require("../parse-log");
|
|
13
|
-
|
|
13
|
+
// A set of custom error messages and their triggering cases,
|
|
14
|
+
// used for syntax error message re-writing when ANTLR would
|
|
15
|
+
// otherwise print a standard (and not very useful) error message.
|
|
16
|
+
//
|
|
17
|
+
// Most Malloy errors are detected and generated in language elements
|
|
18
|
+
// inside `src/lang/ast`. These custom error messages are used to
|
|
19
|
+
// cover a variety of cases that are not as straightforward to
|
|
20
|
+
// catch in the AST.
|
|
21
|
+
exports.malloyCustomErrorCases = [
|
|
14
22
|
{
|
|
15
23
|
errorMessage: "'view:' must be followed by '<identifier> is {'",
|
|
16
24
|
ruleContextOptions: ['exploreQueryDef'],
|
|
@@ -18,7 +26,7 @@ exports.commonErrorCases = [
|
|
|
18
26
|
precedingTokenOptions: [[MalloyParser_1.MalloyParser.VIEW], [MalloyParser_1.MalloyParser.COLON]],
|
|
19
27
|
},
|
|
20
28
|
{
|
|
21
|
-
errorMessage: "Missing '}' at '${
|
|
29
|
+
errorMessage: "Missing '}' at '${offendingSymbol}'",
|
|
22
30
|
ruleContextOptions: ['vExpr'],
|
|
23
31
|
offendingSymbol: MalloyParser_1.MalloyParser.VIEW,
|
|
24
32
|
currentToken: MalloyParser_1.MalloyParser.OCURLY,
|
|
@@ -30,17 +38,106 @@ exports.commonErrorCases = [
|
|
|
30
38
|
'queryProperties',
|
|
31
39
|
'exploreStatement',
|
|
32
40
|
],
|
|
33
|
-
|
|
34
|
-
[MalloyParser_1.MalloyParser.EOF],
|
|
35
|
-
[MalloyParser_1.MalloyParser.RUN],
|
|
36
|
-
[MalloyParser_1.MalloyParser.SOURCE],
|
|
37
|
-
],
|
|
41
|
+
offendingSymbolTextOptions: ['<eof>', 'run:', 'source:'],
|
|
38
42
|
},
|
|
39
43
|
{
|
|
40
44
|
errorMessage: "'aggregate:' entries must include a name (ex: `some_name is count()`)",
|
|
41
45
|
precedingTokenOptions: [[MalloyParser_1.MalloyParser.AGGREGATE]],
|
|
42
46
|
lookAheadOptions: [[-MalloyParser_1.MalloyParser.IS]],
|
|
43
|
-
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
errorMessage: "Expected ':' following 'source'",
|
|
50
|
+
offendingSymbol: MalloyParser_1.MalloyParser.SOURCE_KW,
|
|
51
|
+
ruleContextOptions: ['malloyDocument'],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
errorMessage: "Expected ':' following '${offendingSymbol}'",
|
|
55
|
+
offendingSymbolTextOptions: [
|
|
56
|
+
'dimension',
|
|
57
|
+
'measure',
|
|
58
|
+
'where',
|
|
59
|
+
'declare',
|
|
60
|
+
'join_one',
|
|
61
|
+
'join_many',
|
|
62
|
+
'join_cross',
|
|
63
|
+
'primary_key',
|
|
64
|
+
],
|
|
65
|
+
ruleContextOptions: ['exploreStatement'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
errorMessage: "Expected 'is' or '(' following identifier '${previousToken}'",
|
|
69
|
+
ruleContextOptions: ['sourceDefinition'],
|
|
70
|
+
lookbackSiblingRuleOptions: [
|
|
71
|
+
MalloyParser_1.MalloyParser.RULE_sourceNameDef,
|
|
72
|
+
MalloyParser_1.MalloyParser.RULE_sourceParameters,
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
errorMessage: "Unexpected '{' following source expression. Expected: 'extend', 'include', '+' or '->'",
|
|
77
|
+
offendingSymbol: MalloyParser_1.MalloyParser.OCURLY,
|
|
78
|
+
ruleContextOptions: ['malloyDocument'],
|
|
79
|
+
predecessorHasAncestorRule: MalloyParser_1.MalloyParser.RULE_sqExplore,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
errorMessage: "Unexpected 'join:'. Did you mean 'join_one:', 'join_many:' or 'join_cross:'?",
|
|
83
|
+
ruleContextOptions: ['exploreStatement'],
|
|
84
|
+
offendingSymbolTextOptions: ['join'],
|
|
85
|
+
lookAheadOptions: [[MalloyParser_1.MalloyParser.COLON]],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
errorMessage: "Unexpected '${offendingSymbol}'. Did you mean 'primary_key:'?",
|
|
89
|
+
ruleContextOptions: ['exploreStatement'],
|
|
90
|
+
offendingSymbolTextOptions: ['primarykey', 'primary'],
|
|
91
|
+
lookAheadOptions: [
|
|
92
|
+
[MalloyParser_1.MalloyParser.COLON],
|
|
93
|
+
['key', MalloyParser_1.MalloyParser.COLON],
|
|
94
|
+
['key', MalloyParser_1.MalloyParser.IDENTIFIER],
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
errorMessage: "Unexpected '${offendingSymbol}'. Did you mean 'group_by:'?",
|
|
99
|
+
ruleContextOptions: ['queryStatement'],
|
|
100
|
+
offendingSymbolTextOptions: ['groupby', 'group'],
|
|
101
|
+
lookAheadOptions: [
|
|
102
|
+
[MalloyParser_1.MalloyParser.COLON],
|
|
103
|
+
['by', MalloyParser_1.MalloyParser.COLON],
|
|
104
|
+
['by', MalloyParser_1.MalloyParser.IDENTIFIER],
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
errorMessage: "Unexpected '${offendingSymbol}'. Did you mean 'order_by:'?",
|
|
109
|
+
ruleContextOptions: ['queryStatement'],
|
|
110
|
+
offendingSymbolTextOptions: ['orderby', 'order'],
|
|
111
|
+
lookAheadOptions: [
|
|
112
|
+
[MalloyParser_1.MalloyParser.COLON],
|
|
113
|
+
['by', MalloyParser_1.MalloyParser.COLON],
|
|
114
|
+
['by', MalloyParser_1.MalloyParser.IDENTIFIER],
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
errorMessage: "Expected ':' following '${offendingSymbol}'",
|
|
119
|
+
offendingSymbolTextOptions: [
|
|
120
|
+
'group_by',
|
|
121
|
+
'declare',
|
|
122
|
+
'join_one',
|
|
123
|
+
'join_many',
|
|
124
|
+
'join_cross',
|
|
125
|
+
'extend',
|
|
126
|
+
'select',
|
|
127
|
+
'project',
|
|
128
|
+
'index',
|
|
129
|
+
'aggregate',
|
|
130
|
+
'calculate',
|
|
131
|
+
'top',
|
|
132
|
+
'limit',
|
|
133
|
+
'order_by',
|
|
134
|
+
'where',
|
|
135
|
+
'having',
|
|
136
|
+
'nest',
|
|
137
|
+
'sample',
|
|
138
|
+
'timezone',
|
|
139
|
+
],
|
|
140
|
+
ruleContextOptions: ['queryStatement'],
|
|
44
141
|
},
|
|
45
142
|
];
|
|
46
143
|
class MalloyParserErrorListener {
|
|
@@ -57,7 +154,7 @@ class MalloyParserErrorListener {
|
|
|
57
154
|
const range = offendingSymbol
|
|
58
155
|
? this.translator.rangeFromToken(offendingSymbol)
|
|
59
156
|
: { start: errAt, end: errAt };
|
|
60
|
-
const overrideMessage = (0, custom_error_messages_1.checkCustomErrorMessage)(recognizer, offendingSymbol, exports.
|
|
157
|
+
const overrideMessage = (0, custom_error_messages_1.checkCustomErrorMessage)(recognizer, offendingSymbol, exports.malloyCustomErrorCases);
|
|
61
158
|
if (overrideMessage) {
|
|
62
159
|
message = overrideMessage;
|
|
63
160
|
}
|
|
@@ -8,62 +8,91 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const test_translator_1 = require("./test-translator");
|
|
10
10
|
require("./parse-expects");
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Unit tests intended to cover the custom error messages defined in
|
|
13
|
+
* malloy-custom-error-messages.ts.
|
|
14
|
+
*
|
|
15
|
+
* These tests are intended to prevent unintended regressions. It is
|
|
16
|
+
* okay to change or remove tests here as long as you are doing so
|
|
17
|
+
* for a good reason.
|
|
18
|
+
*/
|
|
19
|
+
describe('custom error messages', () => {
|
|
20
|
+
describe('source', () => {
|
|
21
|
+
test('missing alias', () => {
|
|
22
|
+
expect('source: x extend { }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected 'is' or '(' following identifier 'x'"));
|
|
23
|
+
});
|
|
24
|
+
test('missing closing curly: EOF', () => {
|
|
25
|
+
expect('source: x is a extend {').toLogAtLeast((0, test_translator_1.errorMessage)("Missing '}' at '<EOF>'"));
|
|
26
|
+
});
|
|
27
|
+
test('source: missing colon in root context', () => {
|
|
28
|
+
expect('source x is a extend { }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'source'"));
|
|
29
|
+
});
|
|
30
|
+
test('opening curly to EOF', () => {
|
|
31
|
+
expect(`
|
|
32
|
+
source: y is x extend {
|
|
33
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("Missing '}' at '<EOF>'"));
|
|
34
|
+
});
|
|
35
|
+
test('expression missing operand before curly', () => {
|
|
36
|
+
expect("source: a is presto.table('malloytest.state_facts') {}").toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected '{' following source expression. Expected: 'extend', 'include', '+' or '->'"));
|
|
37
|
+
});
|
|
38
|
+
test('missing opening curly after source extend keyword', () => {
|
|
39
|
+
expect(`
|
|
40
|
+
source: x is a extend
|
|
41
|
+
primary_key: id
|
|
42
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("missing '{' at 'primary_key:'"));
|
|
43
|
+
});
|
|
14
44
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
45
|
+
describe('exploreProperties', () => {
|
|
46
|
+
test('misspelled "join"', () => {
|
|
47
|
+
expect(`source: x is a extend {
|
|
48
|
+
join: data is y on dataId = data.id
|
|
49
|
+
}`).toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'join:'. Did you mean 'join_one:', 'join_many:' or 'join_cross:'?"));
|
|
50
|
+
expect(`source: x is a extend {
|
|
51
|
+
primary_key: name
|
|
52
|
+
join: data is y on dataId = data.id
|
|
53
|
+
}`).toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'join:'. Did you mean 'join_one:', 'join_many:' or 'join_cross:'?"));
|
|
54
|
+
});
|
|
55
|
+
test('misspelled "primary_key:"', () => {
|
|
56
|
+
expect('source: x is a extend { primaryKey: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'primaryKey'. Did you mean 'primary_key:'?"));
|
|
57
|
+
expect('source: x is a extend { primary key: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'primary'. Did you mean 'primary_key:'?"));
|
|
58
|
+
});
|
|
59
|
+
test('explore statement keyword missing colon', () => {
|
|
60
|
+
expect('source: x is a extend { where x > 5 }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'where'"));
|
|
61
|
+
expect('source: x is a extend { primary_key name }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'primary_key'"));
|
|
62
|
+
expect('source: x is a extend { declare total is value.sum() }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'declare'"));
|
|
63
|
+
});
|
|
64
|
+
test('incorrect opening curly after dimension', () => {
|
|
65
|
+
expect(`
|
|
66
|
+
source: x is a extend {
|
|
67
|
+
dimension: {
|
|
68
|
+
test is best
|
|
69
|
+
}
|
|
20
70
|
}
|
|
21
|
-
|
|
22
|
-
|
|
71
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("extraneous input '{' expecting {BQ_STRING, IDENTIFIER}"));
|
|
72
|
+
});
|
|
23
73
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
74
|
+
describe('view', () => {
|
|
75
|
+
test('view is missing name', () => {
|
|
76
|
+
expect(`
|
|
77
|
+
source: x is a extend { view: {
|
|
78
|
+
group_by: b
|
|
79
|
+
} }
|
|
80
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("'view:' must be followed by '<identifier> is {'"));
|
|
81
|
+
});
|
|
82
|
+
test('missing closing curly, source>view', () => {
|
|
83
|
+
expect(`
|
|
84
|
+
source: x is a extend {
|
|
85
|
+
view: y is {
|
|
86
|
+
group_by: b
|
|
29
87
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
`).toLogAtLeast((0, test_translator_1.errorMessage)("Missing '}' at 'view:'"));
|
|
35
|
-
});
|
|
36
|
-
test('incorrect opening curly after dimension', () => {
|
|
37
|
-
expect(`
|
|
38
|
-
source: x is a extend {
|
|
39
|
-
dimension: {
|
|
40
|
-
test is best
|
|
88
|
+
view: z is {
|
|
89
|
+
group_by: c
|
|
90
|
+
}
|
|
41
91
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expect(`
|
|
47
|
-
source: x is a extend {
|
|
48
|
-
primarykey: id
|
|
49
|
-
}
|
|
50
|
-
`).toLogAtLeast((0, test_translator_1.errorMessage)("no viable alternative at input 'primarykey'"));
|
|
51
|
-
});
|
|
52
|
-
test('missing opening curly after source extend keyword', () => {
|
|
53
|
-
expect(`
|
|
54
|
-
source: x is a extend
|
|
55
|
-
primary_key: id
|
|
56
|
-
`).toLogAtLeast((0, test_translator_1.errorMessage)("Missing '{' after 'extend'"));
|
|
57
|
-
});
|
|
58
|
-
test('missing alias for aggregate entry', () => {
|
|
59
|
-
expect(`
|
|
60
|
-
run: x -> {
|
|
61
|
-
aggregate: count()
|
|
62
|
-
}
|
|
63
|
-
`).toLogAtLeast((0, test_translator_1.errorMessage)("'aggregate:' entries must include a name (ex: `some_name is count()`)"));
|
|
64
|
-
});
|
|
65
|
-
test('missing alias for aggregate inside source>view', () => {
|
|
66
|
-
expect(`
|
|
92
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("Missing '}' at 'view:'"));
|
|
93
|
+
});
|
|
94
|
+
test('missing alias for aggregate inside source>view', () => {
|
|
95
|
+
expect(`
|
|
67
96
|
source: x is aa extend {
|
|
68
97
|
measure: airport_count is count()
|
|
69
98
|
|
|
@@ -73,16 +102,41 @@ describe('errors', () => {
|
|
|
73
102
|
}
|
|
74
103
|
}
|
|
75
104
|
`).toLogAtLeast((0, test_translator_1.errorMessage)("'aggregate:' entries must include a name (ex: `some_name is count()`)"));
|
|
105
|
+
});
|
|
76
106
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
describe('queryStatement', () => {
|
|
108
|
+
test('misspelled group_by:', () => {
|
|
109
|
+
expect('source: x is a -> { groupBy: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'groupBy'. Did you mean 'group_by:'?"));
|
|
110
|
+
expect('source: x is a -> { group by: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'group'. Did you mean 'group_by:'?"));
|
|
111
|
+
expect('source: x is a -> { group by name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'group'. Did you mean 'group_by:'?"));
|
|
112
|
+
});
|
|
113
|
+
test('misspelled order_by:', () => {
|
|
114
|
+
expect('source: x is a -> { orderBy: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'orderBy'. Did you mean 'order_by:'?"));
|
|
115
|
+
expect('source: x is a -> { order by: name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'order'. Did you mean 'order_by:'?"));
|
|
116
|
+
expect('source: x is a -> { order by name }').toLogAtLeast((0, test_translator_1.errorMessage)("Unexpected 'order'. Did you mean 'order_by:'?"));
|
|
117
|
+
});
|
|
118
|
+
test('query statement keyword missing colon', () => {
|
|
119
|
+
expect('source: x is a -> { group_by x > 5 }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'group_by'"));
|
|
120
|
+
expect('source: x is a -> { declare name is id }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'declare'"));
|
|
121
|
+
expect('source: x is a -> { select * }').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'select'"));
|
|
122
|
+
expect('source: x is a -> { project i, j, k}').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'project'"));
|
|
123
|
+
expect('source: x is a -> { where val > 5}').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'where'"));
|
|
124
|
+
expect('source: x is a -> { order_by name desc, 2 asc}').toLogAtLeast((0, test_translator_1.errorMessage)("Expected ':' following 'order_by'"));
|
|
125
|
+
});
|
|
81
126
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
127
|
+
describe('run', () => {
|
|
128
|
+
test('missing alias for aggregate entry', () => {
|
|
129
|
+
expect(`
|
|
130
|
+
run: x -> {
|
|
131
|
+
aggregate: count()
|
|
132
|
+
}
|
|
133
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("'aggregate:' entries must include a name (ex: `some_name is count()`)"));
|
|
134
|
+
});
|
|
135
|
+
test('run opening curly to EOF', () => {
|
|
136
|
+
expect(`
|
|
137
|
+
run: x -> {
|
|
138
|
+
`).toLogAtLeast((0, test_translator_1.errorMessage)("Missing '}' at '<EOF>'"));
|
|
139
|
+
});
|
|
86
140
|
});
|
|
87
141
|
});
|
|
88
142
|
//# sourceMappingURL=syntax-errors.spec.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloydata/malloy",
|
|
3
|
-
"version": "0.0.237-
|
|
3
|
+
"version": "0.0.237-dev250225213433",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@malloydata/malloy-interfaces": "^0.0.237-
|
|
45
|
-
"@malloydata/malloy-tag": "^0.0.237-
|
|
44
|
+
"@malloydata/malloy-interfaces": "^0.0.237-dev250225213433",
|
|
45
|
+
"@malloydata/malloy-tag": "^0.0.237-dev250225213433",
|
|
46
46
|
"antlr4ts": "^0.5.0-alpha.4",
|
|
47
47
|
"assert": "^2.0.0",
|
|
48
48
|
"jest-diff": "^29.6.2",
|