bobe 0.0.35 → 0.0.37

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/index.d.ts CHANGED
@@ -174,6 +174,33 @@ 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
+ declare enum ParseErrorCode {
178
+ UNCLOSED_BRACE = 9001,
179
+ UNCLOSED_STRING = 9002,
180
+ UNCLOSED_STATIC_INS = 9003,
181
+ INCONSISTENT_INDENT = 9004,
182
+ INDENT_MISMATCH = 9005,
183
+ MISSING_ASSIGN = 9006,
184
+ INVALID_TAG_NAME = 9007,
185
+ ELSE_WITHOUT_IF = 9008,
186
+ EMPTY_IF_BODY = 9009,
187
+ EMPTY_FOR_BODY = 9010,
188
+ MISSING_FOR_COLLECTION = 9011,
189
+ MISSING_FOR_SEMICOLON = 9012,
190
+ MISSING_FOR_ITEM = 9013,
191
+ PIPE_IN_WRONG_CONTEXT = 9014
192
+ }
193
+ type ParseError = {
194
+ code: ParseErrorCode;
195
+ message: string;
196
+ loc: SourceLocation;
197
+ };
198
+ /** tokenizer 抛出的带位置信息的语法错误 */
199
+ declare class ParseSyntaxError extends SyntaxError {
200
+ code: ParseErrorCode;
201
+ loc: SourceLocation;
202
+ constructor(code: ParseErrorCode, message: string, loc: SourceLocation);
203
+ }
177
204
  type ProgramCtx = {
178
205
  stack: MultiTypeStack<any>;
179
206
  prevSibling: any;
@@ -271,6 +298,9 @@ declare class Tokenizer {
271
298
  constructor(hook: Hook, useDedentAsEof: boolean);
272
299
  private next;
273
300
  getCurrentPos(): Position;
301
+ /** 构造从当前扫描起始位置到模板结尾的 SourceLocation,用于未闭合错误 */
302
+ private unclosedLoc;
303
+ private throwUnclosed;
274
304
  resume(_snapshot: ReturnType<Tokenizer['snapshot']>): void;
275
305
  snapshot(keys?: (keyof Tokenizer)[]): Partial<Tokenizer>;
276
306
  skip(): string;
@@ -295,6 +325,7 @@ declare class Tokenizer {
295
325
  private brace;
296
326
  private newLine;
297
327
  private getDentValue;
328
+ emptyLoc(): SourceLocation;
298
329
  private dent;
299
330
  private shorterThanBaseDentEof;
300
331
  private identifier;
@@ -395,7 +426,9 @@ interface FragmentNode extends BaseNode {
395
426
  declare class Compiler {
396
427
  tokenizer: Tokenizer;
397
428
  hooks: ParseHooks;
429
+ errors: ParseError[];
398
430
  constructor(tokenizer: Tokenizer, hooks?: ParseHooks);
431
+ private addError;
399
432
  /**
400
433
  * 编译程序入口,生成AST
401
434
  */
@@ -458,5 +491,5 @@ type ParseHooks = Partial<{
458
491
  declare function bobe(fragments: TemplateStringsArray, ...values: any[]): BobeUI;
459
492
  declare function customRender(option: CustomRenderConf): <T>(Ctor: typeof Store, root: any) => (ComponentNode$1 | Store)[];
460
493
 
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 };
494
+ export { Compiler, NodeType, ParseErrorCode, ParseSyntaxError, Tokenizer, bobe, customRender };
495
+ export type { ASTNodeType, BaseNode, ComponentNode, ConditionalNode, DynamicValue, ElementNode, FragmentNode, InterpolationNode, LoopNode, ParseError, Program, Property, PropertyKeyNode, PropertyValue, SourceLocation, StaticValue, TemplateNode, TextNode };
package/dist/index.umd.js CHANGED
@@ -44,6 +44,30 @@
44
44
  TerpEvt["HandledComponentNode"] = "handled-component-node";
45
45
  return TerpEvt;
46
46
  })({});
47
+ let ParseErrorCode = function (ParseErrorCode) {
48
+ ParseErrorCode[ParseErrorCode["UNCLOSED_BRACE"] = 9001] = "UNCLOSED_BRACE";
49
+ ParseErrorCode[ParseErrorCode["UNCLOSED_STRING"] = 9002] = "UNCLOSED_STRING";
50
+ ParseErrorCode[ParseErrorCode["UNCLOSED_STATIC_INS"] = 9003] = "UNCLOSED_STATIC_INS";
51
+ ParseErrorCode[ParseErrorCode["INCONSISTENT_INDENT"] = 9004] = "INCONSISTENT_INDENT";
52
+ ParseErrorCode[ParseErrorCode["INDENT_MISMATCH"] = 9005] = "INDENT_MISMATCH";
53
+ ParseErrorCode[ParseErrorCode["MISSING_ASSIGN"] = 9006] = "MISSING_ASSIGN";
54
+ ParseErrorCode[ParseErrorCode["INVALID_TAG_NAME"] = 9007] = "INVALID_TAG_NAME";
55
+ ParseErrorCode[ParseErrorCode["ELSE_WITHOUT_IF"] = 9008] = "ELSE_WITHOUT_IF";
56
+ ParseErrorCode[ParseErrorCode["EMPTY_IF_BODY"] = 9009] = "EMPTY_IF_BODY";
57
+ ParseErrorCode[ParseErrorCode["EMPTY_FOR_BODY"] = 9010] = "EMPTY_FOR_BODY";
58
+ ParseErrorCode[ParseErrorCode["MISSING_FOR_COLLECTION"] = 9011] = "MISSING_FOR_COLLECTION";
59
+ ParseErrorCode[ParseErrorCode["MISSING_FOR_SEMICOLON"] = 9012] = "MISSING_FOR_SEMICOLON";
60
+ ParseErrorCode[ParseErrorCode["MISSING_FOR_ITEM"] = 9013] = "MISSING_FOR_ITEM";
61
+ ParseErrorCode[ParseErrorCode["PIPE_IN_WRONG_CONTEXT"] = 9014] = "PIPE_IN_WRONG_CONTEXT";
62
+ return ParseErrorCode;
63
+ }({});
64
+ class ParseSyntaxError extends SyntaxError {
65
+ constructor(code, message, loc) {
66
+ super(message);
67
+ this.code = code;
68
+ this.loc = loc;
69
+ }
70
+ }
47
71
 
48
72
  class Tokenizer {
49
73
  TabSize = 2;
@@ -82,6 +106,25 @@
82
106
  column: this.column
83
107
  };
84
108
  }
109
+ unclosedLoc(startOffset, startLine, startCol) {
110
+ const end = this.code.length - 1;
111
+ return {
112
+ start: {
113
+ offset: startOffset,
114
+ line: startLine,
115
+ column: startCol
116
+ },
117
+ end: {
118
+ offset: end,
119
+ line: this.line,
120
+ column: this.column
121
+ },
122
+ source: this.code.slice(startOffset, end)
123
+ };
124
+ }
125
+ throwUnclosed(code, message, startOffset, startLine, startCol) {
126
+ throw new ParseSyntaxError(code, message, this.unclosedLoc(startOffset, startLine, startCol));
127
+ }
85
128
  resume(_snapshot) {
86
129
  this.token = undefined;
87
130
  this.needIndent = false;
@@ -135,7 +178,7 @@
135
178
  const expLen = this.dentStack[i];
136
179
  if (currLen === expLen) break;
137
180
  if (currLen > expLen) {
138
- throw SyntaxError(`缩进错误,缩进长度不匹配`);
181
+ throw new ParseSyntaxError(ParseErrorCode.INCONSISTENT_INDENT, '缩进大小不统一', this.emptyLoc());
139
182
  }
140
183
  if (this.shorterThanBaseDentEof()) {
141
184
  break;
@@ -250,8 +293,7 @@
250
293
  }
251
294
  return this.token;
252
295
  } catch (error) {
253
- console.error(error);
254
- return this.token;
296
+ throw error;
255
297
  } finally {
256
298
  this.handledTokens.push(this.token);
257
299
  }
@@ -314,6 +356,9 @@
314
356
  this.setToken(TokenType.Pipe, '|');
315
357
  }
316
358
  staticIns() {
359
+ const startOffset = this.preI,
360
+ startLine = this.line,
361
+ startCol = this.preCol;
317
362
  let nextC = this.code[this.i + 1];
318
363
  if (nextC !== '{') {
319
364
  return false;
@@ -323,6 +368,9 @@
323
368
  let innerBrace = 0;
324
369
  while (1) {
325
370
  nextC = this.code[this.i + 1];
371
+ if (nextC === undefined) {
372
+ this.throwUnclosed(ParseErrorCode.UNCLOSED_STATIC_INS, '未闭合的 "${...}"', startOffset, startLine, startCol);
373
+ }
326
374
  value += nextC;
327
375
  this.next();
328
376
  if (nextC === '{') {
@@ -339,6 +387,9 @@
339
387
  return true;
340
388
  }
341
389
  brace() {
390
+ const startOffset = this.preI,
391
+ startLine = this.line,
392
+ startCol = this.preCol;
342
393
  let inComment,
343
394
  inString,
344
395
  count = 0,
@@ -346,6 +397,9 @@
346
397
  backslashCount = 0;
347
398
  while (1) {
348
399
  const char = this.code[this.i];
400
+ if (char === undefined) {
401
+ this.throwUnclosed(ParseErrorCode.UNCLOSED_BRACE, '未闭合的 "{"', startOffset, startLine, startCol);
402
+ }
349
403
  const nextChar = this.code[this.i + 1];
350
404
  if (inComment === 'single' && char === '\n') {
351
405
  inComment = null;
@@ -433,6 +487,18 @@
433
487
  isEmptyLine
434
488
  };
435
489
  }
490
+ emptyLoc() {
491
+ const pos = this.getCurrentPos();
492
+ return {
493
+ start: pos,
494
+ end: {
495
+ offset: pos.offset + 1,
496
+ line: pos.line,
497
+ column: pos.column + 1
498
+ },
499
+ source: ' '
500
+ };
501
+ }
436
502
  dent() {
437
503
  const _this$getDentValue2 = this.getDentValue(),
438
504
  value = _this$getDentValue2.value,
@@ -459,7 +525,7 @@
459
525
  const expLen = this.dentStack[i];
460
526
  if (currLen === expLen) break;
461
527
  if (currLen > expLen) {
462
- throw SyntaxError('缩进大小不统一');
528
+ throw new ParseSyntaxError(ParseErrorCode.INCONSISTENT_INDENT, '缩进大小不统一', this.emptyLoc());
463
529
  }
464
530
  if (this.shorterThanBaseDentEof()) {
465
531
  return;
@@ -528,11 +594,17 @@
528
594
  this.setToken(TokenType.Identifier, realValue);
529
595
  }
530
596
  str(char) {
597
+ const startOffset = this.preI,
598
+ startLine = this.line,
599
+ startCol = this.preCol;
531
600
  let value = '';
532
601
  let nextC;
533
602
  let continuousBackslashCount = 0;
534
603
  while (1) {
535
604
  nextC = this.code[this.i + 1];
605
+ if (nextC === undefined) {
606
+ this.throwUnclosed(ParseErrorCode.UNCLOSED_STRING, '未闭合的字符串字面量', startOffset, startLine, startCol);
607
+ }
536
608
  const memoCount = continuousBackslashCount;
537
609
  if (nextC === '\\') {
538
610
  continuousBackslashCount++;
@@ -840,18 +912,33 @@
840
912
  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
913
  _initProto = _applyDecs$e[0];
842
914
  }
915
+ errors = (_initProto(this), []);
843
916
  constructor(tokenizer, hooks = {}) {
844
917
  this.tokenizer = tokenizer;
845
918
  this.hooks = hooks;
846
- _initProto(this);
919
+ }
920
+ addError(code, message, loc) {
921
+ this.errors.push({
922
+ code,
923
+ message,
924
+ loc
925
+ });
847
926
  }
848
927
  parseProgram() {
849
- this.tokenizer.nextToken();
850
928
  const body = [];
851
- while (!this.tokenizer.isEof()) {
852
- const node = this.templateNode();
853
- if (node) {
854
- body.push(node);
929
+ try {
930
+ this.tokenizer.nextToken();
931
+ while (!this.tokenizer.isEof()) {
932
+ const node = this.templateNode(body);
933
+ if (node) {
934
+ body.push(node);
935
+ }
936
+ }
937
+ } catch (error) {
938
+ if (error instanceof ParseSyntaxError) {
939
+ this.addError(error.code, error.message, error.loc);
940
+ } else {
941
+ this.addError(error.toString(), '未知错误', this.tokenizer.emptyLoc());
855
942
  }
856
943
  }
857
944
  return {
@@ -877,7 +964,7 @@
877
964
  if (this.tokenizer.token.type & TokenType.Indent) {
878
965
  this.tokenizer.nextToken();
879
966
  while (!(this.tokenizer.token.type & TokenType.Dedent) && !this.tokenizer.isEof()) {
880
- const child = this.templateNode();
967
+ const child = this.templateNode(children);
881
968
  if (child) {
882
969
  children.push(child);
883
970
  }
@@ -888,13 +975,26 @@
888
975
  }
889
976
  return children;
890
977
  }
891
- templateNode() {
892
- this.tokenizer.token;
978
+ templateNode(siblings) {
979
+ const token = this.tokenizer.token;
980
+ if (token.type & TokenType.Pipe) {
981
+ this.addError(ParseErrorCode.PIPE_IN_WRONG_CONTEXT, '"|" 只能出现在元素属性扩展行中', token.loc ?? this.tokenizer.emptyLoc());
982
+ this.tokenizer.nextToken();
983
+ return null;
984
+ }
893
985
  const _this$tokenizer$_hook = this.tokenizer._hook({}),
894
986
  _this$tokenizer$_hook2 = _slicedToArray(_this$tokenizer$_hook, 2),
895
987
  hookType = _this$tokenizer$_hook2[0],
896
988
  value = _this$tokenizer$_hook2[1];
897
- if (value === 'if' || value === 'else' || value === 'fail') {
989
+ const isElseOrFail = value === 'else' || value === 'fail';
990
+ if (value === 'if' || isElseOrFail) {
991
+ if (isElseOrFail) {
992
+ const lastSibling = siblings[siblings.length - 1];
993
+ const lastType = lastSibling?.type;
994
+ if (lastType !== NodeType.If && lastType !== NodeType.Else && lastType !== NodeType.Fail) {
995
+ this.addError(ParseErrorCode.ELSE_WITHOUT_IF, `"${value}" 前必须有 "if" 或 "else" 节点`, token.loc ?? this.tokenizer.emptyLoc());
996
+ }
997
+ }
898
998
  return this.parseConditionalNode();
899
999
  }
900
1000
  if (value === 'for') {
@@ -919,6 +1019,13 @@
919
1019
  }
920
1020
  parseElementNode(node) {
921
1021
  const tagToken = this.tokenizer.token;
1022
+ if (!(tagToken.type & TokenType.Identifier)) {
1023
+ this.addError(ParseErrorCode.INVALID_TAG_NAME, `无效的标签名,期望标识符但得到 "${tagToken.value}"`, tagToken.loc ?? this.tokenizer.emptyLoc());
1024
+ while (!(this.tokenizer.token.type & TokenType.NewLine) && !this.tokenizer.isEof()) {
1025
+ this.tokenizer.nextToken();
1026
+ }
1027
+ return null;
1028
+ }
922
1029
  const tagName = tagToken.value;
923
1030
  this.tokenizer.nextToken();
924
1031
  const props = this.headerLineAndExtensions();
@@ -944,15 +1051,25 @@
944
1051
  return node;
945
1052
  }
946
1053
  parseLoopNode(node) {
1054
+ const forLoc = this.tokenizer.token.loc ?? this.tokenizer.emptyLoc();
947
1055
  this.tokenizer.nextToken();
948
1056
  const collection = this.parsePropertyValue();
949
- this.tokenizer.nextToken();
1057
+ if (!collection.value && collection.value !== 0) {
1058
+ this.addError(ParseErrorCode.MISSING_FOR_COLLECTION, '"for" 缺少集合表达式', forLoc);
1059
+ }
1060
+ const semicolonToken = this.tokenizer.nextToken();
1061
+ if (!(semicolonToken.type & TokenType.Semicolon)) {
1062
+ this.addError(ParseErrorCode.MISSING_FOR_SEMICOLON, '"for" 语法:for <集合>; <item> [index][; key],缺少第一个 ";"', semicolonToken.loc ?? this.tokenizer.emptyLoc());
1063
+ }
950
1064
  const itemToken = this.tokenizer.nextToken();
951
1065
  const isDestruct = itemToken.type === TokenType.InsertionExp;
952
1066
  if (isDestruct) {
953
1067
  itemToken.value = '{' + itemToken.value + '}';
954
1068
  }
955
1069
  const item = this.parsePropertyValue();
1070
+ if (!item.value && item.value !== 0) {
1071
+ this.addError(ParseErrorCode.MISSING_FOR_ITEM, '"for" 缺少 item 变量名', itemToken.loc ?? this.tokenizer.emptyLoc());
1072
+ }
956
1073
  let char = this.tokenizer.peekChar(),
957
1074
  key,
958
1075
  index;
@@ -1015,6 +1132,8 @@
1015
1132
  this.tokenizer.nextToken();
1016
1133
  node.value = this.parsePropertyValue();
1017
1134
  this.tokenizer.nextToken();
1135
+ } else {
1136
+ this.addError(ParseErrorCode.MISSING_ASSIGN, `属性 "${node.key.key}" 缺少 "=" 赋值符号`, node.key.loc ?? this.tokenizer.emptyLoc());
1018
1137
  }
1019
1138
  node.loc.start = node.key.loc.start;
1020
1139
  node.loc.end = node.value ? node.value.loc.end : node.key.loc.end;
@@ -2014,6 +2133,7 @@
2014
2133
 
2015
2134
  exports.Compiler = Compiler;
2016
2135
  exports.NodeType = NodeType;
2136
+ exports.ParseSyntaxError = ParseSyntaxError;
2017
2137
  exports.Tokenizer = Tokenizer;
2018
2138
  exports.bobe = bobe;
2019
2139
  exports.customRender = customRender;