kopscript 0.7.2 → 0.9.0
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/LLM.md +17 -8
- package/README.md +29 -3
- package/dist/checker.js +89 -89
- package/dist/cli.js +12 -2
- package/dist/codegen.js +162 -17
- package/dist/diagnostics.js +5 -5
- package/dist/lexer.js +4 -4
- package/dist/modules.js +21 -14
- package/dist/parser.js +22 -22
- package/dist/sourcemap.js +113 -0
- package/dist/template_compiler.js +1 -1
- package/dist/template_lexer.js +2 -2
- package/dist/template_parser.js +12 -12
- package/package.json +1 -1
package/dist/parser.js
CHANGED
|
@@ -16,7 +16,7 @@ export class Parser {
|
|
|
16
16
|
while (!this.check(TokenKind.EOF)) {
|
|
17
17
|
if (this.check(TokenKind.Using)) {
|
|
18
18
|
const t = this.peek();
|
|
19
|
-
this.diagnostics.error("'using' directives must appear at the top of the file, before any other declaration", t.line, t.col);
|
|
19
|
+
this.diagnostics.error("KS2001", "'using' directives must appear at the top of the file, before any other declaration", t.line, t.col);
|
|
20
20
|
this.parseUsing(); // consume and discard so parsing can continue
|
|
21
21
|
continue;
|
|
22
22
|
}
|
|
@@ -44,7 +44,7 @@ export class Parser {
|
|
|
44
44
|
const expr = this.parseExpression();
|
|
45
45
|
if (!this.check(TokenKind.EOF)) {
|
|
46
46
|
const t = this.peek();
|
|
47
|
-
this.diagnostics.error(`Unexpected token '${t.lexeme}' after expression`, t.line, t.col);
|
|
47
|
+
this.diagnostics.error("KS2002", `Unexpected token '${t.lexeme}' after expression`, t.line, t.col);
|
|
48
48
|
}
|
|
49
49
|
return expr;
|
|
50
50
|
}
|
|
@@ -60,7 +60,7 @@ export class Parser {
|
|
|
60
60
|
const type = this.parseType();
|
|
61
61
|
if (!this.check(TokenKind.EOF)) {
|
|
62
62
|
const t = this.peek();
|
|
63
|
-
this.diagnostics.error(`Unexpected token '${t.lexeme}' after type`, t.line, t.col);
|
|
63
|
+
this.diagnostics.error("KS2003", `Unexpected token '${t.lexeme}' after type`, t.line, t.col);
|
|
64
64
|
}
|
|
65
65
|
return type;
|
|
66
66
|
}
|
|
@@ -152,11 +152,11 @@ export class Parser {
|
|
|
152
152
|
if (this.isDeclStart()) {
|
|
153
153
|
const decl = this.parseDeclaration(isExported);
|
|
154
154
|
if (decl.kind === "VarDecl") {
|
|
155
|
-
this.diagnostics.error("'public'/'private' cannot modify a variable declaration", modifierTok.line, modifierTok.col);
|
|
155
|
+
this.diagnostics.error("KS2004", "'public'/'private' cannot modify a variable declaration", modifierTok.line, modifierTok.col);
|
|
156
156
|
}
|
|
157
157
|
return decl;
|
|
158
158
|
}
|
|
159
|
-
this.diagnostics.error("Expected a class, interface, enum, or function declaration after 'public'/'private'", modifierTok.line, modifierTok.col);
|
|
159
|
+
this.diagnostics.error("KS2005", "Expected a class, interface, enum, or function declaration after 'public'/'private'", modifierTok.line, modifierTok.col);
|
|
160
160
|
throw new ParseError();
|
|
161
161
|
}
|
|
162
162
|
// Lookahead for the C#-style `Type name` declaration shape (locals and
|
|
@@ -204,7 +204,7 @@ export class Parser {
|
|
|
204
204
|
return { kind: "FunctionDecl", isExported, isAsync, name, params, returnType: type, body, line: start.line, col: start.col };
|
|
205
205
|
}
|
|
206
206
|
if (isAsync) {
|
|
207
|
-
this.diagnostics.error("'async' cannot modify a variable declaration", start.line, start.col);
|
|
207
|
+
this.diagnostics.error("KS2006", "'async' cannot modify a variable declaration", start.line, start.col);
|
|
208
208
|
}
|
|
209
209
|
this.consume(TokenKind.Assign, "Expected '=' in variable declaration");
|
|
210
210
|
const init = this.parseExpression();
|
|
@@ -273,7 +273,7 @@ export class Parser {
|
|
|
273
273
|
if (this.check(TokenKind.Template)) {
|
|
274
274
|
const templateStart = this.advance();
|
|
275
275
|
if (template) {
|
|
276
|
-
this.diagnostics.error(`Class '${name}' already has a 'template' declaration`, templateStart.line, templateStart.col);
|
|
276
|
+
this.diagnostics.error("KS2007", `Class '${name}' already has a 'template' declaration`, templateStart.line, templateStart.col);
|
|
277
277
|
}
|
|
278
278
|
this.consume(TokenKind.From, "Expected 'from \"<path>\"' after 'template'");
|
|
279
279
|
const pathTok = this.consume(TokenKind.String, "Expected a file path string after 'from'");
|
|
@@ -333,7 +333,7 @@ export class Parser {
|
|
|
333
333
|
const params = this.parseParamList();
|
|
334
334
|
const body = this.parseBlock();
|
|
335
335
|
if (isStatic && (isVirtual || isOverride)) {
|
|
336
|
-
this.diagnostics.error("'static' methods cannot be 'virtual' or 'override'", memberStart.line, memberStart.col);
|
|
336
|
+
this.diagnostics.error("KS2008", "'static' methods cannot be 'virtual' or 'override'", memberStart.line, memberStart.col);
|
|
337
337
|
}
|
|
338
338
|
methods.push({
|
|
339
339
|
kind: "MethodDecl",
|
|
@@ -354,13 +354,13 @@ export class Parser {
|
|
|
354
354
|
}
|
|
355
355
|
else if (this.check(TokenKind.LBrace)) {
|
|
356
356
|
if (isVirtual || isOverride) {
|
|
357
|
-
this.diagnostics.error("'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
|
|
357
|
+
this.diagnostics.error("KS2009", "'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
|
|
358
358
|
}
|
|
359
359
|
if (isAsync) {
|
|
360
|
-
this.diagnostics.error("'async' is only valid on methods", memberStart.line, memberStart.col);
|
|
360
|
+
this.diagnostics.error("KS2010", "'async' is only valid on methods", memberStart.line, memberStart.col);
|
|
361
361
|
}
|
|
362
362
|
if (isStatic) {
|
|
363
|
-
this.diagnostics.error("'static' properties are not supported in v1 (use a static field instead)", memberStart.line, memberStart.col);
|
|
363
|
+
this.diagnostics.error("KS2011", "'static' properties are not supported in v1 (use a static field instead)", memberStart.line, memberStart.col);
|
|
364
364
|
}
|
|
365
365
|
this.advance(); // '{'
|
|
366
366
|
this.consume(TokenKind.Get, "Expected 'get' in property accessor list");
|
|
@@ -386,10 +386,10 @@ export class Parser {
|
|
|
386
386
|
}
|
|
387
387
|
else {
|
|
388
388
|
if (isVirtual || isOverride) {
|
|
389
|
-
this.diagnostics.error("'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
|
|
389
|
+
this.diagnostics.error("KS2012", "'virtual'/'override' are only valid on methods", memberStart.line, memberStart.col);
|
|
390
390
|
}
|
|
391
391
|
if (isAsync) {
|
|
392
|
-
this.diagnostics.error("'async' is only valid on methods", memberStart.line, memberStart.col);
|
|
392
|
+
this.diagnostics.error("KS2013", "'async' is only valid on methods", memberStart.line, memberStart.col);
|
|
393
393
|
}
|
|
394
394
|
let initializer = null;
|
|
395
395
|
if (isStatic) {
|
|
@@ -500,7 +500,7 @@ export class Parser {
|
|
|
500
500
|
const start = this.advance(); // 'raw'
|
|
501
501
|
const typeTok = this.consume(TokenKind.Identifier, "Expected 'string' after 'raw'");
|
|
502
502
|
if (typeTok.lexeme !== "string") {
|
|
503
|
-
this.diagnostics.error(`'raw' declarations must be of type 'string', got '${typeTok.lexeme}'`, typeTok.line, typeTok.col);
|
|
503
|
+
this.diagnostics.error("KS2014", `'raw' declarations must be of type 'string', got '${typeTok.lexeme}'`, typeTok.line, typeTok.col);
|
|
504
504
|
}
|
|
505
505
|
const name = this.consume(TokenKind.Identifier, "Expected name").lexeme;
|
|
506
506
|
this.consume(TokenKind.From, "Expected 'from \"<path>\"' after 'raw string <Name>'");
|
|
@@ -531,10 +531,10 @@ export class Parser {
|
|
|
531
531
|
if (this.check(TokenKind.Constructor)) {
|
|
532
532
|
const ctorTok = this.advance();
|
|
533
533
|
if (virtualTok) {
|
|
534
|
-
this.diagnostics.error(`'virtual' is not valid on an extern constructor`, virtualTok.line, virtualTok.col);
|
|
534
|
+
this.diagnostics.error("KS2015", `'virtual' is not valid on an extern constructor`, virtualTok.line, virtualTok.col);
|
|
535
535
|
}
|
|
536
536
|
if (hasConstructor) {
|
|
537
|
-
this.diagnostics.error(`Extern class '${name}' already has a constructor signature`, ctorTok.line, ctorTok.col);
|
|
537
|
+
this.diagnostics.error("KS2016", `Extern class '${name}' already has a constructor signature`, ctorTok.line, ctorTok.col);
|
|
538
538
|
}
|
|
539
539
|
ctorParams = this.parseParamList();
|
|
540
540
|
hasConstructor = true;
|
|
@@ -550,7 +550,7 @@ export class Parser {
|
|
|
550
550
|
}
|
|
551
551
|
else if (this.check(TokenKind.LBrace)) {
|
|
552
552
|
if (virtualTok) {
|
|
553
|
-
this.diagnostics.error(`'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
|
|
553
|
+
this.diagnostics.error("KS2017", `'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
|
|
554
554
|
}
|
|
555
555
|
this.advance();
|
|
556
556
|
this.consume(TokenKind.Get, "Expected 'get' in extern property accessor list");
|
|
@@ -566,7 +566,7 @@ export class Parser {
|
|
|
566
566
|
}
|
|
567
567
|
else {
|
|
568
568
|
if (virtualTok) {
|
|
569
|
-
this.diagnostics.error(`'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
|
|
569
|
+
this.diagnostics.error("KS2018", `'virtual' is not valid on an extern property`, virtualTok.line, virtualTok.col);
|
|
570
570
|
}
|
|
571
571
|
this.consume(TokenKind.Semicolon, "Expected ';' after extern property declaration");
|
|
572
572
|
properties.push({ isStatic, name: memberName, type, hasSetter: true });
|
|
@@ -671,7 +671,7 @@ export class Parser {
|
|
|
671
671
|
finallyBlock = this.parseBlock();
|
|
672
672
|
}
|
|
673
673
|
if (!catchBlock && !finallyBlock) {
|
|
674
|
-
this.diagnostics.error("'try' must be followed by 'catch' and/or 'finally'", start.line, start.col);
|
|
674
|
+
this.diagnostics.error("KS2019", "'try' must be followed by 'catch' and/or 'finally'", start.line, start.col);
|
|
675
675
|
}
|
|
676
676
|
return { kind: "TryStatement", tryBlock, catchParam, catchBlock, finallyBlock, line: start.line, col: start.col };
|
|
677
677
|
}
|
|
@@ -945,7 +945,7 @@ export class Parser {
|
|
|
945
945
|
this.consume(TokenKind.RParen, "Expected ')' after expression");
|
|
946
946
|
return expr;
|
|
947
947
|
}
|
|
948
|
-
this.diagnostics.error(`Unexpected token '${t.lexeme || t.kind}'`, t.line, t.col);
|
|
948
|
+
this.diagnostics.error("KS2020", `Unexpected token '${t.lexeme || t.kind}'`, t.line, t.col);
|
|
949
949
|
throw new ParseError();
|
|
950
950
|
}
|
|
951
951
|
// Distinguishes a lambda's parameter list from a parenthesized expression.
|
|
@@ -1049,7 +1049,7 @@ export class Parser {
|
|
|
1049
1049
|
const subParser = new Parser(subTokens, subDiagnostics);
|
|
1050
1050
|
const expr = subParser.parseExpression();
|
|
1051
1051
|
for (const d of subDiagnostics.diagnostics) {
|
|
1052
|
-
this.diagnostics.error(d.message, token.line, token.col);
|
|
1052
|
+
this.diagnostics.error("KS2021", d.message, token.line, token.col);
|
|
1053
1053
|
}
|
|
1054
1054
|
return expr;
|
|
1055
1055
|
}
|
|
@@ -1109,7 +1109,7 @@ export class Parser {
|
|
|
1109
1109
|
if (this.check(kind))
|
|
1110
1110
|
return this.advance();
|
|
1111
1111
|
const t = this.peek();
|
|
1112
|
-
this.diagnostics.error(message, t.line, t.col);
|
|
1112
|
+
this.diagnostics.error("KS2022", message, t.line, t.col);
|
|
1113
1113
|
throw new ParseError();
|
|
1114
1114
|
}
|
|
1115
1115
|
synchronize() {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// A minimal, self-contained source-map v3 (base64 VLQ) encoder — hand-rolled
|
|
2
|
+
// rather than pulling in the `source-map` package, to keep kopscript at zero
|
|
3
|
+
// runtime dependencies (see README.md/package.json). No imports beyond this
|
|
4
|
+
// file's own code; nothing here reads or writes the filesystem.
|
|
5
|
+
//
|
|
6
|
+
// Spec: https://sourcemaps.info/spec.html — the encoding this file
|
|
7
|
+
// implements (VLQ digit format, per-line column reset) is described there.
|
|
8
|
+
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
9
|
+
const VLQ_BASE_SHIFT = 5;
|
|
10
|
+
const VLQ_BASE = 1 << VLQ_BASE_SHIFT; // 32 — 5 data bits per base64 digit
|
|
11
|
+
const VLQ_BASE_MASK = VLQ_BASE - 1; // 0x1F
|
|
12
|
+
const VLQ_CONTINUATION_BIT = VLQ_BASE; // 0x20 — set on every digit but the last
|
|
13
|
+
// Sign-and-magnitude, not two's complement: the sign lives in the low bit of
|
|
14
|
+
// the shifted value, not as a separate leading digit — this is what makes a
|
|
15
|
+
// small negative delta (the common case for a line's *second* segment
|
|
16
|
+
// resetting a column back down) encode just as compactly as a small
|
|
17
|
+
// positive one.
|
|
18
|
+
function toSignedVLQ(value) {
|
|
19
|
+
return value < 0 ? (-value << 1) + 1 : value << 1;
|
|
20
|
+
}
|
|
21
|
+
function encodeVLQValue(value) {
|
|
22
|
+
let vlq = toSignedVLQ(value);
|
|
23
|
+
let result = "";
|
|
24
|
+
do {
|
|
25
|
+
let digit = vlq & VLQ_BASE_MASK;
|
|
26
|
+
vlq >>>= VLQ_BASE_SHIFT;
|
|
27
|
+
if (vlq > 0)
|
|
28
|
+
digit |= VLQ_CONTINUATION_BIT;
|
|
29
|
+
result += BASE64_CHARS[digit];
|
|
30
|
+
} while (vlq > 0);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
// Encodes one mapping segment's fields (already converted to deltas by the
|
|
34
|
+
// caller) as a contiguous run of VLQ digit-groups — e.g. `[0, 0, 0, 0]` (the
|
|
35
|
+
// "no change from the previous segment" case) always encodes to `"AAAA"`.
|
|
36
|
+
export function encodeVLQ(values) {
|
|
37
|
+
return values.map(encodeVLQValue).join("");
|
|
38
|
+
}
|
|
39
|
+
// Builds one file's source-map v3 JSON incrementally, one `addMapping` call
|
|
40
|
+
// per source position worth recording (see codegen.ts for how call sites are
|
|
41
|
+
// chosen — statement-level, not full expression/column granularity). Only
|
|
42
|
+
// ever describes a single source file per generated file, matching
|
|
43
|
+
// kopscript's one-.ks-file-in, one-.js-file-out compilation model.
|
|
44
|
+
export class SourceMapBuilder {
|
|
45
|
+
constructor(fileName, sourceFileName, sourceContent) {
|
|
46
|
+
// `mappings` is one comma-joined segment-string per GENERATED line,
|
|
47
|
+
// joined with `;` at the end — an empty array entry for a generated line
|
|
48
|
+
// with no recorded mapping produces the required-but-empty group between
|
|
49
|
+
// two semicolons, not a skipped line (that would shift every later line's
|
|
50
|
+
// meaning, since position in the `;`-separated list *is* the line number).
|
|
51
|
+
this.lineGroups = [];
|
|
52
|
+
// Per the spec, `generatedColumn` deltas reset to being measured from 0
|
|
53
|
+
// at the start of every generated line, but `sourceLine`/`sourceColumn`
|
|
54
|
+
// deltas accumulate across the *entire* file, never resetting — tracking
|
|
55
|
+
// both correctly is the one genuinely easy-to-get-wrong part of this.
|
|
56
|
+
this.prevGeneratedColumn = 0;
|
|
57
|
+
this.prevSourceLine = 0;
|
|
58
|
+
this.prevSourceColumn = 0;
|
|
59
|
+
this.fileName = fileName;
|
|
60
|
+
this.sourceFileName = sourceFileName;
|
|
61
|
+
this.sourceContent = sourceContent;
|
|
62
|
+
}
|
|
63
|
+
lineGroup(generatedLine) {
|
|
64
|
+
const index = generatedLine - 1;
|
|
65
|
+
while (this.lineGroups.length <= index)
|
|
66
|
+
this.lineGroups.push([]);
|
|
67
|
+
return this.lineGroups[index];
|
|
68
|
+
}
|
|
69
|
+
// `generatedLine`/`sourceLine`/`sourceColumn` are 1-indexed (kopscript's
|
|
70
|
+
// own convention throughout the lexer/parser/AST — see lexer.ts). Per the
|
|
71
|
+
// source-map spec itself, everything is 0-indexed in the encoded output;
|
|
72
|
+
// the `- 1`s below are that conversion, done in exactly one place.
|
|
73
|
+
// `generatedColumn` is the real (already 0-indexed) character offset from
|
|
74
|
+
// the start of its generated line — there's no separate convention to
|
|
75
|
+
// convert there since "0 spaces of indent" already means column 0.
|
|
76
|
+
addMapping(generatedLine, generatedColumn, sourceLine, sourceColumn) {
|
|
77
|
+
const group = this.lineGroup(generatedLine);
|
|
78
|
+
if (group.length === 0)
|
|
79
|
+
this.prevGeneratedColumn = 0; // new line: column deltas restart at 0
|
|
80
|
+
const zSourceLine = sourceLine - 1;
|
|
81
|
+
const zSourceColumn = sourceColumn - 1;
|
|
82
|
+
group.push(encodeVLQ([
|
|
83
|
+
generatedColumn - this.prevGeneratedColumn,
|
|
84
|
+
0, // sourceIndex delta — always 0, exactly one source file per map
|
|
85
|
+
zSourceLine - this.prevSourceLine,
|
|
86
|
+
zSourceColumn - this.prevSourceColumn,
|
|
87
|
+
]));
|
|
88
|
+
this.prevGeneratedColumn = generatedColumn;
|
|
89
|
+
this.prevSourceLine = zSourceLine;
|
|
90
|
+
this.prevSourceColumn = zSourceColumn;
|
|
91
|
+
}
|
|
92
|
+
// Prepends `count` empty line-groups — for when marks were recorded
|
|
93
|
+
// relative to a chunk's own start (line 1) before it was known how many
|
|
94
|
+
// lines of *other* content would end up preceding that chunk in the final
|
|
95
|
+
// output. See codegen.ts's `generate()`: body statements are marked
|
|
96
|
+
// before it's known whether the `state<T>` prelude will precede them.
|
|
97
|
+
shiftLines(count) {
|
|
98
|
+
if (count <= 0)
|
|
99
|
+
return;
|
|
100
|
+
this.lineGroups.unshift(...Array.from({ length: count }, () => []));
|
|
101
|
+
}
|
|
102
|
+
toJSON() {
|
|
103
|
+
const mappings = this.lineGroups.map((segments) => segments.join(",")).join(";");
|
|
104
|
+
return JSON.stringify({
|
|
105
|
+
version: 3,
|
|
106
|
+
file: this.fileName,
|
|
107
|
+
sources: [this.sourceFileName],
|
|
108
|
+
sourcesContent: [this.sourceContent],
|
|
109
|
+
names: [],
|
|
110
|
+
mappings,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -222,7 +222,7 @@ export class TemplateCompiler {
|
|
|
222
222
|
const elementChildren = node.children.filter((c) => c.kind === "element");
|
|
223
223
|
const textChildren = node.children.filter((c) => c.kind === "text");
|
|
224
224
|
if (textChildren.length > 0 && elementChildren.length > 0) {
|
|
225
|
-
this.diagnostics.error(`<${node.tag}> cannot mix text and element children — Kopular's DOM bindings have no text-node API, so text can only be a whole element's content`, node.line, node.col);
|
|
225
|
+
this.diagnostics.error("KS5015", `<${node.tag}> cannot mix text and element children — Kopular's DOM bindings have no text-node API, so text can only be a whole element's content`, node.line, node.col);
|
|
226
226
|
}
|
|
227
227
|
else if (textChildren.length > 0) {
|
|
228
228
|
const parts = textChildren.flatMap((t) => t.parts).map((p) => (p.kind === "Expr" ? { kind: "Expr", expression: this.resolve(p.expression, localScope) } : p));
|
package/dist/template_lexer.js
CHANGED
|
@@ -130,7 +130,7 @@ export class TemplateLexer {
|
|
|
130
130
|
while (!this.isAtEnd() && this.peek() !== '"')
|
|
131
131
|
value += this.advance();
|
|
132
132
|
if (this.isAtEnd()) {
|
|
133
|
-
this.diagnostics.error("Unterminated attribute value", line, col);
|
|
133
|
+
this.diagnostics.error("KS5001", "Unterminated attribute value", line, col);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
136
|
this.advance(); // closing '"'
|
|
@@ -143,7 +143,7 @@ export class TemplateLexer {
|
|
|
143
143
|
while (!this.isAtEnd() && !/[\s=>/]/.test(this.peek()))
|
|
144
144
|
name += this.advance();
|
|
145
145
|
if (name.length === 0) {
|
|
146
|
-
this.diagnostics.error(`Unexpected character '${this.peek()}' in tag`, line, col);
|
|
146
|
+
this.diagnostics.error("KS5002", `Unexpected character '${this.peek()}' in tag`, line, col);
|
|
147
147
|
this.advance(); // avoid an infinite loop on a genuinely unexpected character
|
|
148
148
|
return this.nextTagToken();
|
|
149
149
|
}
|
package/dist/template_parser.js
CHANGED
|
@@ -44,7 +44,7 @@ export class TemplateParser {
|
|
|
44
44
|
const expr = new Parser(tokens, localDiagnostics).parseStandaloneExpression();
|
|
45
45
|
for (const d of localDiagnostics.diagnostics) {
|
|
46
46
|
const { line, col } = remapPosition(fragmentLine, fragmentCol, d.line, d.col);
|
|
47
|
-
this.diagnostics.error(d.message, line, col);
|
|
47
|
+
this.diagnostics.error("KS5003", d.message, line, col);
|
|
48
48
|
}
|
|
49
49
|
return expr;
|
|
50
50
|
}
|
|
@@ -57,7 +57,7 @@ export class TemplateParser {
|
|
|
57
57
|
const tokens = new Lexer(source, localDiagnostics).tokenize();
|
|
58
58
|
const ofIndex = tokens.findIndex((t) => t.kind === TokenKind.Identifier && t.lexeme === "of");
|
|
59
59
|
if (ofIndex < 1 || tokens[ofIndex - 1].kind !== TokenKind.Identifier) {
|
|
60
|
-
this.diagnostics.error(`Expected '*for="Type varName of iterable"', got '${source}'`, fragmentLine, fragmentCol);
|
|
60
|
+
this.diagnostics.error("KS5004", `Expected '*for="Type varName of iterable"', got '${source}'`, fragmentLine, fragmentCol);
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
const varNameToken = tokens[ofIndex - 1];
|
|
@@ -67,7 +67,7 @@ export class TemplateParser {
|
|
|
67
67
|
const iterable = new Parser(iterableTokens, localDiagnostics).parseStandaloneExpression();
|
|
68
68
|
for (const d of localDiagnostics.diagnostics) {
|
|
69
69
|
const { line, col } = remapPosition(fragmentLine, fragmentCol, d.line, d.col);
|
|
70
|
-
this.diagnostics.error(d.message, line, col);
|
|
70
|
+
this.diagnostics.error("KS5005", d.message, line, col);
|
|
71
71
|
}
|
|
72
72
|
return { varType, varName: varNameToken.lexeme, iterable, line: fragmentLine, col: fragmentCol };
|
|
73
73
|
}
|
|
@@ -104,7 +104,7 @@ export class TemplateParser {
|
|
|
104
104
|
const exprCol = col;
|
|
105
105
|
advancePos(text.slice(i, exprStartOffset));
|
|
106
106
|
if (end === -1) {
|
|
107
|
-
this.diagnostics.error("Unterminated '{{' interpolation", exprLine, exprCol);
|
|
107
|
+
this.diagnostics.error("KS5006", "Unterminated '{{' interpolation", exprLine, exprCol);
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
const exprSource = text.slice(exprStartOffset, end);
|
|
@@ -139,7 +139,7 @@ export class TemplateParser {
|
|
|
139
139
|
const elementRoots = roots.filter((r) => r.kind === "element");
|
|
140
140
|
if (elementRoots.length !== 1) {
|
|
141
141
|
const at = roots[0] ?? { line: 1, col: 1 };
|
|
142
|
-
this.diagnostics.error(`A template must have exactly one top-level element, found ${elementRoots.length}`, at.line, at.col);
|
|
142
|
+
this.diagnostics.error("KS5007", `A template must have exactly one top-level element, found ${elementRoots.length}`, at.line, at.col);
|
|
143
143
|
return null;
|
|
144
144
|
}
|
|
145
145
|
return elementRoots[0];
|
|
@@ -153,7 +153,7 @@ export class TemplateParser {
|
|
|
153
153
|
return this.parseElement();
|
|
154
154
|
}
|
|
155
155
|
const t = this.advance();
|
|
156
|
-
this.diagnostics.error(`Unexpected token in template ('${t.lexeme}')`, t.line, t.col);
|
|
156
|
+
this.diagnostics.error("KS5008", `Unexpected token in template ('${t.lexeme}')`, t.line, t.col);
|
|
157
157
|
return null;
|
|
158
158
|
}
|
|
159
159
|
parseElement() {
|
|
@@ -183,7 +183,7 @@ export class TemplateParser {
|
|
|
183
183
|
}
|
|
184
184
|
else if (name === "*if") {
|
|
185
185
|
if (ifCondition || forBinding) {
|
|
186
|
-
this.diagnostics.error(`Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
|
|
186
|
+
this.diagnostics.error("KS5009", `Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
|
|
187
187
|
}
|
|
188
188
|
else {
|
|
189
189
|
ifCondition = this.parseEmbeddedExpression(valueTok.lexeme, valueTok.line, valueTok.col);
|
|
@@ -191,7 +191,7 @@ export class TemplateParser {
|
|
|
191
191
|
}
|
|
192
192
|
else if (name === "*for") {
|
|
193
193
|
if (ifCondition || forBinding) {
|
|
194
|
-
this.diagnostics.error(`Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
|
|
194
|
+
this.diagnostics.error("KS5010", `Only one structural directive ('*if'/'*for') is allowed per element`, nameTok.line, nameTok.col);
|
|
195
195
|
}
|
|
196
196
|
else {
|
|
197
197
|
forBinding = this.parseForBinding(valueTok.lexeme, valueTok.line, valueTok.col);
|
|
@@ -219,11 +219,11 @@ export class TemplateParser {
|
|
|
219
219
|
if (this.check(TemplateTokenKind.TagClose)) {
|
|
220
220
|
const closeTok = this.advance();
|
|
221
221
|
if (closeTok.lexeme !== tag) {
|
|
222
|
-
this.diagnostics.error(`Mismatched closing tag: expected '</${tag}>', got '</${closeTok.lexeme}>'`, closeTok.line, closeTok.col);
|
|
222
|
+
this.diagnostics.error("KS5011", `Mismatched closing tag: expected '</${tag}>', got '</${closeTok.lexeme}>'`, closeTok.line, closeTok.col);
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
else {
|
|
226
|
-
this.diagnostics.error(`Expected closing tag '</${tag}>'`, tagTok.line, tagTok.col);
|
|
226
|
+
this.diagnostics.error("KS5012", `Expected closing tag '</${tag}>'`, tagTok.line, tagTok.col);
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
return { kind: "element", tag, staticAttrs, propBindings, eventBindings, ifCondition, forBinding, children, line: tagTok.line, col: tagTok.col };
|
|
@@ -233,7 +233,7 @@ export class TemplateParser {
|
|
|
233
233
|
}
|
|
234
234
|
consumeAttrValue(attrNameTok) {
|
|
235
235
|
if (!this.check(TemplateTokenKind.AttrValue)) {
|
|
236
|
-
this.diagnostics.error(`Expected a quoted value after '${attrNameTok.lexeme}='`, attrNameTok.line, attrNameTok.col);
|
|
236
|
+
this.diagnostics.error("KS5013", `Expected a quoted value after '${attrNameTok.lexeme}='`, attrNameTok.line, attrNameTok.col);
|
|
237
237
|
return null;
|
|
238
238
|
}
|
|
239
239
|
return this.advance();
|
|
@@ -243,7 +243,7 @@ export class TemplateParser {
|
|
|
243
243
|
this.advance();
|
|
244
244
|
}
|
|
245
245
|
else {
|
|
246
|
-
this.diagnostics.error(message, at.line, at.col);
|
|
246
|
+
this.diagnostics.error("KS5014", message, at.line, at.col);
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
}
|
package/package.json
CHANGED