bobe 0.0.35 → 0.0.36
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/bobe.cjs.js +116 -12
- package/dist/bobe.cjs.js.map +1 -1
- package/dist/bobe.compiler.cjs.js +116 -12
- package/dist/bobe.compiler.cjs.js.map +1 -1
- package/dist/bobe.compiler.esm.js +116 -13
- package/dist/bobe.compiler.esm.js.map +1 -1
- package/dist/bobe.esm.js +116 -13
- package/dist/bobe.esm.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.umd.js +116 -12
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -174,6 +174,18 @@ type TerpConf = Partial<Pick<Interpreter, 'createNode' | 'setProp' | 'insertAfte
|
|
|
174
174
|
type CustomRenderConf = Pick<TerpConf, 'createNode' | 'setProp' | 'insertAfter' | 'remove' | 'createAnchor' | 'firstChild' | 'nextSib'>;
|
|
175
175
|
type Hook = (props: HookProps) => any;
|
|
176
176
|
type HookType = 'dynamic' | 'static';
|
|
177
|
+
type ParseErrorCode = 'UNCLOSED_BRACE' | 'UNCLOSED_STRING' | 'UNCLOSED_STATIC_INS' | 'INCONSISTENT_INDENT' | 'INDENT_MISMATCH' | 'MISSING_ASSIGN' | 'INVALID_TAG_NAME' | 'ELSE_WITHOUT_IF' | 'EMPTY_IF_BODY' | 'EMPTY_FOR_BODY' | 'MISSING_FOR_COLLECTION' | 'MISSING_FOR_SEMICOLON' | 'MISSING_FOR_ITEM' | 'PIPE_IN_WRONG_CONTEXT';
|
|
178
|
+
type ParseError = {
|
|
179
|
+
code: ParseErrorCode;
|
|
180
|
+
message: string;
|
|
181
|
+
loc: SourceLocation;
|
|
182
|
+
};
|
|
183
|
+
/** tokenizer 抛出的带位置信息的语法错误 */
|
|
184
|
+
declare class ParseSyntaxError extends SyntaxError {
|
|
185
|
+
code: ParseErrorCode;
|
|
186
|
+
loc: SourceLocation;
|
|
187
|
+
constructor(code: ParseErrorCode, message: string, loc: SourceLocation);
|
|
188
|
+
}
|
|
177
189
|
type ProgramCtx = {
|
|
178
190
|
stack: MultiTypeStack<any>;
|
|
179
191
|
prevSibling: any;
|
|
@@ -271,6 +283,9 @@ declare class Tokenizer {
|
|
|
271
283
|
constructor(hook: Hook, useDedentAsEof: boolean);
|
|
272
284
|
private next;
|
|
273
285
|
getCurrentPos(): Position;
|
|
286
|
+
/** 构造从当前扫描起始位置到模板结尾的 SourceLocation,用于未闭合错误 */
|
|
287
|
+
private unclosedLoc;
|
|
288
|
+
private throwUnclosed;
|
|
274
289
|
resume(_snapshot: ReturnType<Tokenizer['snapshot']>): void;
|
|
275
290
|
snapshot(keys?: (keyof Tokenizer)[]): Partial<Tokenizer>;
|
|
276
291
|
skip(): string;
|
|
@@ -395,7 +410,10 @@ interface FragmentNode extends BaseNode {
|
|
|
395
410
|
declare class Compiler {
|
|
396
411
|
tokenizer: Tokenizer;
|
|
397
412
|
hooks: ParseHooks;
|
|
413
|
+
errors: ParseError[];
|
|
398
414
|
constructor(tokenizer: Tokenizer, hooks?: ParseHooks);
|
|
415
|
+
private addError;
|
|
416
|
+
private emptyLoc;
|
|
399
417
|
/**
|
|
400
418
|
* 编译程序入口,生成AST
|
|
401
419
|
*/
|
|
@@ -458,5 +476,5 @@ type ParseHooks = Partial<{
|
|
|
458
476
|
declare function bobe(fragments: TemplateStringsArray, ...values: any[]): BobeUI;
|
|
459
477
|
declare function customRender(option: CustomRenderConf): <T>(Ctor: typeof Store, root: any) => (ComponentNode$1 | Store)[];
|
|
460
478
|
|
|
461
|
-
export { Compiler, NodeType, Tokenizer, bobe, customRender };
|
|
462
|
-
export type { ASTNodeType, BaseNode, ComponentNode, ConditionalNode, DynamicValue, ElementNode, FragmentNode, InterpolationNode, LoopNode, Program, Property, PropertyKeyNode, PropertyValue, SourceLocation, StaticValue, TemplateNode, TextNode };
|
|
479
|
+
export { Compiler, NodeType, ParseSyntaxError, Tokenizer, bobe, customRender };
|
|
480
|
+
export type { ASTNodeType, BaseNode, ComponentNode, ConditionalNode, DynamicValue, ElementNode, FragmentNode, InterpolationNode, LoopNode, ParseError, ParseErrorCode, Program, Property, PropertyKeyNode, PropertyValue, SourceLocation, StaticValue, TemplateNode, TextNode };
|
package/dist/index.umd.js
CHANGED
|
@@ -44,6 +44,13 @@
|
|
|
44
44
|
TerpEvt["HandledComponentNode"] = "handled-component-node";
|
|
45
45
|
return TerpEvt;
|
|
46
46
|
})({});
|
|
47
|
+
class ParseSyntaxError extends SyntaxError {
|
|
48
|
+
constructor(code, message, loc) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.loc = loc;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
47
54
|
|
|
48
55
|
class Tokenizer {
|
|
49
56
|
TabSize = 2;
|
|
@@ -82,6 +89,25 @@
|
|
|
82
89
|
column: this.column
|
|
83
90
|
};
|
|
84
91
|
}
|
|
92
|
+
unclosedLoc(startOffset, startLine, startCol) {
|
|
93
|
+
const end = this.code.length - 1;
|
|
94
|
+
return {
|
|
95
|
+
start: {
|
|
96
|
+
offset: startOffset,
|
|
97
|
+
line: startLine,
|
|
98
|
+
column: startCol
|
|
99
|
+
},
|
|
100
|
+
end: {
|
|
101
|
+
offset: end,
|
|
102
|
+
line: this.line,
|
|
103
|
+
column: this.column
|
|
104
|
+
},
|
|
105
|
+
source: this.code.slice(startOffset, end)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
throwUnclosed(code, message, startOffset, startLine, startCol) {
|
|
109
|
+
throw new ParseSyntaxError(code, message, this.unclosedLoc(startOffset, startLine, startCol));
|
|
110
|
+
}
|
|
85
111
|
resume(_snapshot) {
|
|
86
112
|
this.token = undefined;
|
|
87
113
|
this.needIndent = false;
|
|
@@ -250,8 +276,7 @@
|
|
|
250
276
|
}
|
|
251
277
|
return this.token;
|
|
252
278
|
} catch (error) {
|
|
253
|
-
|
|
254
|
-
return this.token;
|
|
279
|
+
throw error;
|
|
255
280
|
} finally {
|
|
256
281
|
this.handledTokens.push(this.token);
|
|
257
282
|
}
|
|
@@ -314,6 +339,9 @@
|
|
|
314
339
|
this.setToken(TokenType.Pipe, '|');
|
|
315
340
|
}
|
|
316
341
|
staticIns() {
|
|
342
|
+
const startOffset = this.preI,
|
|
343
|
+
startLine = this.line,
|
|
344
|
+
startCol = this.preCol;
|
|
317
345
|
let nextC = this.code[this.i + 1];
|
|
318
346
|
if (nextC !== '{') {
|
|
319
347
|
return false;
|
|
@@ -323,6 +351,9 @@
|
|
|
323
351
|
let innerBrace = 0;
|
|
324
352
|
while (1) {
|
|
325
353
|
nextC = this.code[this.i + 1];
|
|
354
|
+
if (nextC === undefined) {
|
|
355
|
+
this.throwUnclosed('UNCLOSED_STATIC_INS', '未闭合的 "${...}"', startOffset, startLine, startCol);
|
|
356
|
+
}
|
|
326
357
|
value += nextC;
|
|
327
358
|
this.next();
|
|
328
359
|
if (nextC === '{') {
|
|
@@ -339,6 +370,9 @@
|
|
|
339
370
|
return true;
|
|
340
371
|
}
|
|
341
372
|
brace() {
|
|
373
|
+
const startOffset = this.preI,
|
|
374
|
+
startLine = this.line,
|
|
375
|
+
startCol = this.preCol;
|
|
342
376
|
let inComment,
|
|
343
377
|
inString,
|
|
344
378
|
count = 0,
|
|
@@ -346,6 +380,9 @@
|
|
|
346
380
|
backslashCount = 0;
|
|
347
381
|
while (1) {
|
|
348
382
|
const char = this.code[this.i];
|
|
383
|
+
if (char === undefined) {
|
|
384
|
+
this.throwUnclosed('UNCLOSED_BRACE', '未闭合的 "{"', startOffset, startLine, startCol);
|
|
385
|
+
}
|
|
349
386
|
const nextChar = this.code[this.i + 1];
|
|
350
387
|
if (inComment === 'single' && char === '\n') {
|
|
351
388
|
inComment = null;
|
|
@@ -528,11 +565,17 @@
|
|
|
528
565
|
this.setToken(TokenType.Identifier, realValue);
|
|
529
566
|
}
|
|
530
567
|
str(char) {
|
|
568
|
+
const startOffset = this.preI,
|
|
569
|
+
startLine = this.line,
|
|
570
|
+
startCol = this.preCol;
|
|
531
571
|
let value = '';
|
|
532
572
|
let nextC;
|
|
533
573
|
let continuousBackslashCount = 0;
|
|
534
574
|
while (1) {
|
|
535
575
|
nextC = this.code[this.i + 1];
|
|
576
|
+
if (nextC === undefined) {
|
|
577
|
+
this.throwUnclosed('UNCLOSED_STRING', '未闭合的字符串字面量', startOffset, startLine, startCol);
|
|
578
|
+
}
|
|
536
579
|
const memoCount = continuousBackslashCount;
|
|
537
580
|
if (nextC === '\\') {
|
|
538
581
|
continuousBackslashCount++;
|
|
@@ -840,18 +883,47 @@
|
|
|
840
883
|
var _applyDecs$e = _slicedToArray(_applyDecs2311(this, [], [[NodeHook, 2, "parseProgram"], [[NodeHook, NodeLoc], 2, "parseComponentNode"], [[NodeHook, NodeLoc], 2, "parseElementNode"], [[NodeHook, NodeLoc], 2, "parseConditionalNode"], [[NodeHook, NodeLoc], 2, "parseLoopNode"], [NodeHook, 2, "parseProperty"], [[NodeHook, TokenLoc], 2, "parsePropertyKey"], [[NodeHook, TokenLoc], 2, "parsePropertyValue"], [[NodeHook, TokenLoc], 2, "parseName"]]).e, 1);
|
|
841
884
|
_initProto = _applyDecs$e[0];
|
|
842
885
|
}
|
|
886
|
+
errors = (_initProto(this), []);
|
|
843
887
|
constructor(tokenizer, hooks = {}) {
|
|
844
888
|
this.tokenizer = tokenizer;
|
|
845
889
|
this.hooks = hooks;
|
|
846
|
-
|
|
890
|
+
}
|
|
891
|
+
addError(code, message, loc) {
|
|
892
|
+
this.errors.push({
|
|
893
|
+
code,
|
|
894
|
+
message,
|
|
895
|
+
loc
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
emptyLoc() {
|
|
899
|
+
const pos = this.tokenizer.getCurrentPos();
|
|
900
|
+
return {
|
|
901
|
+
start: pos,
|
|
902
|
+
end: {
|
|
903
|
+
offset: pos.offset + 1,
|
|
904
|
+
line: pos.line,
|
|
905
|
+
column: pos.column + 1
|
|
906
|
+
},
|
|
907
|
+
source: ' '
|
|
908
|
+
};
|
|
847
909
|
}
|
|
848
910
|
parseProgram() {
|
|
849
|
-
this.tokenizer.nextToken();
|
|
850
911
|
const body = [];
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
912
|
+
try {
|
|
913
|
+
this.tokenizer.nextToken();
|
|
914
|
+
while (!this.tokenizer.isEof()) {
|
|
915
|
+
const node = this.templateNode(body);
|
|
916
|
+
if (node) {
|
|
917
|
+
body.push(node);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
} catch (error) {
|
|
921
|
+
if (error instanceof ParseSyntaxError) {
|
|
922
|
+
this.addError(error.code, error.message, error.loc);
|
|
923
|
+
} else if (error instanceof SyntaxError) {
|
|
924
|
+
const knownCodes = ['INCONSISTENT_INDENT', 'INDENT_MISMATCH'];
|
|
925
|
+
const code = knownCodes.includes(error.message) ? error.message : 'INCONSISTENT_INDENT';
|
|
926
|
+
this.addError(code, error.message, this.emptyLoc());
|
|
855
927
|
}
|
|
856
928
|
}
|
|
857
929
|
return {
|
|
@@ -877,7 +949,7 @@
|
|
|
877
949
|
if (this.tokenizer.token.type & TokenType.Indent) {
|
|
878
950
|
this.tokenizer.nextToken();
|
|
879
951
|
while (!(this.tokenizer.token.type & TokenType.Dedent) && !this.tokenizer.isEof()) {
|
|
880
|
-
const child = this.templateNode();
|
|
952
|
+
const child = this.templateNode(children);
|
|
881
953
|
if (child) {
|
|
882
954
|
children.push(child);
|
|
883
955
|
}
|
|
@@ -888,13 +960,25 @@
|
|
|
888
960
|
}
|
|
889
961
|
return children;
|
|
890
962
|
}
|
|
891
|
-
templateNode() {
|
|
892
|
-
this.tokenizer.token;
|
|
963
|
+
templateNode(siblings) {
|
|
964
|
+
const token = this.tokenizer.token;
|
|
965
|
+
if (token.type & TokenType.Pipe) {
|
|
966
|
+
this.addError('PIPE_IN_WRONG_CONTEXT', '"|" 只能出现在元素属性扩展行中', token.loc ?? this.emptyLoc());
|
|
967
|
+
this.tokenizer.nextToken();
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
893
970
|
const _this$tokenizer$_hook = this.tokenizer._hook({}),
|
|
894
971
|
_this$tokenizer$_hook2 = _slicedToArray(_this$tokenizer$_hook, 2),
|
|
895
972
|
hookType = _this$tokenizer$_hook2[0],
|
|
896
973
|
value = _this$tokenizer$_hook2[1];
|
|
897
974
|
if (value === 'if' || value === 'else' || value === 'fail') {
|
|
975
|
+
if (value === 'else' || value === 'fail') {
|
|
976
|
+
const lastSibling = siblings[siblings.length - 1];
|
|
977
|
+
const lastType = lastSibling?.type;
|
|
978
|
+
if (lastType !== NodeType.If && lastType !== NodeType.Else && lastType !== NodeType.Fail) {
|
|
979
|
+
this.addError('ELSE_WITHOUT_IF', `"${value}" 前必须有 "if" 或 "else" 节点`, token.loc ?? this.emptyLoc());
|
|
980
|
+
}
|
|
981
|
+
}
|
|
898
982
|
return this.parseConditionalNode();
|
|
899
983
|
}
|
|
900
984
|
if (value === 'for') {
|
|
@@ -919,6 +1003,13 @@
|
|
|
919
1003
|
}
|
|
920
1004
|
parseElementNode(node) {
|
|
921
1005
|
const tagToken = this.tokenizer.token;
|
|
1006
|
+
if (!(tagToken.type & TokenType.Identifier)) {
|
|
1007
|
+
this.addError('INVALID_TAG_NAME', `无效的标签名,期望标识符但得到 "${tagToken.value}"`, tagToken.loc ?? this.emptyLoc());
|
|
1008
|
+
while (!(this.tokenizer.token.type & TokenType.NewLine) && !this.tokenizer.isEof()) {
|
|
1009
|
+
this.tokenizer.nextToken();
|
|
1010
|
+
}
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
922
1013
|
const tagName = tagToken.value;
|
|
923
1014
|
this.tokenizer.nextToken();
|
|
924
1015
|
const props = this.headerLineAndExtensions();
|
|
@@ -944,15 +1035,25 @@
|
|
|
944
1035
|
return node;
|
|
945
1036
|
}
|
|
946
1037
|
parseLoopNode(node) {
|
|
1038
|
+
const forLoc = this.tokenizer.token.loc ?? this.emptyLoc();
|
|
947
1039
|
this.tokenizer.nextToken();
|
|
948
1040
|
const collection = this.parsePropertyValue();
|
|
949
|
-
|
|
1041
|
+
if (!collection.value && collection.value !== 0) {
|
|
1042
|
+
this.addError('MISSING_FOR_COLLECTION', '"for" 缺少集合表达式', forLoc);
|
|
1043
|
+
}
|
|
1044
|
+
const semicolonToken = this.tokenizer.nextToken();
|
|
1045
|
+
if (!(semicolonToken.type & TokenType.Semicolon)) {
|
|
1046
|
+
this.addError('MISSING_FOR_SEMICOLON', '"for" 语法:for <集合>; <item> [index][; key],缺少第一个 ";"', semicolonToken.loc ?? this.emptyLoc());
|
|
1047
|
+
}
|
|
950
1048
|
const itemToken = this.tokenizer.nextToken();
|
|
951
1049
|
const isDestruct = itemToken.type === TokenType.InsertionExp;
|
|
952
1050
|
if (isDestruct) {
|
|
953
1051
|
itemToken.value = '{' + itemToken.value + '}';
|
|
954
1052
|
}
|
|
955
1053
|
const item = this.parsePropertyValue();
|
|
1054
|
+
if (!item.value && item.value !== 0) {
|
|
1055
|
+
this.addError('MISSING_FOR_ITEM', '"for" 缺少 item 变量名', itemToken.loc ?? this.emptyLoc());
|
|
1056
|
+
}
|
|
956
1057
|
let char = this.tokenizer.peekChar(),
|
|
957
1058
|
key,
|
|
958
1059
|
index;
|
|
@@ -1015,6 +1116,8 @@
|
|
|
1015
1116
|
this.tokenizer.nextToken();
|
|
1016
1117
|
node.value = this.parsePropertyValue();
|
|
1017
1118
|
this.tokenizer.nextToken();
|
|
1119
|
+
} else {
|
|
1120
|
+
this.addError('MISSING_ASSIGN', `属性 "${node.key.key}" 缺少 "=" 赋值符号`, node.key.loc ?? this.emptyLoc());
|
|
1018
1121
|
}
|
|
1019
1122
|
node.loc.start = node.key.loc.start;
|
|
1020
1123
|
node.loc.end = node.value ? node.value.loc.end : node.key.loc.end;
|
|
@@ -2014,6 +2117,7 @@
|
|
|
2014
2117
|
|
|
2015
2118
|
exports.Compiler = Compiler;
|
|
2016
2119
|
exports.NodeType = NodeType;
|
|
2120
|
+
exports.ParseSyntaxError = ParseSyntaxError;
|
|
2017
2121
|
exports.Tokenizer = Tokenizer;
|
|
2018
2122
|
exports.bobe = bobe;
|
|
2019
2123
|
exports.customRender = customRender;
|