rooklang 0.4.1 → 0.5.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/CHANGELOG.md +235 -0
- package/CHANGELOG.ru.md +235 -0
- package/dist/aliases.d.ts +2 -0
- package/dist/aliases.js +83 -0
- package/dist/ast/declarations.d.ts +18 -0
- package/dist/ast/expressions.d.ts +14 -1
- package/dist/ast/markup.d.ts +2 -0
- package/dist/ast/statements.d.ts +2 -2
- package/dist/checker/assignments.js +2 -1
- package/dist/checker/base.d.ts +3 -0
- package/dist/checker/base.js +1 -1
- package/dist/checker/builtins.js +5 -1
- package/dist/checker/calls.d.ts +2 -0
- package/dist/checker/calls.js +18 -3
- package/dist/checker/classes.js +8 -0
- package/dist/checker/component-bindings.d.ts +16 -0
- package/dist/checker/component-bindings.js +65 -2
- package/dist/checker/context.d.ts +12 -0
- package/dist/checker/declarations.d.ts +2 -0
- package/dist/checker/declarations.js +38 -3
- package/dist/checker/expressions.d.ts +1 -0
- package/dist/checker/expressions.js +17 -1
- package/dist/checker/flow.d.ts +1 -1
- package/dist/checker/flow.js +10 -0
- package/dist/checker/functions.d.ts +2 -0
- package/dist/checker/functions.js +33 -7
- package/dist/checker/goroutines.d.ts +2 -2
- package/dist/checker/goroutines.js +2 -2
- package/dist/checker/imports.d.ts +10 -0
- package/dist/checker/imports.js +73 -6
- package/dist/checker/initializers.d.ts +12 -0
- package/dist/checker/initializers.js +80 -0
- package/dist/checker/loops.js +3 -1
- package/dist/checker/members.js +1 -0
- package/dist/checker/resolution.d.ts +2 -0
- package/dist/checker/resolution.js +13 -2
- package/dist/checker/statements.d.ts +5 -0
- package/dist/checker/statements.js +26 -3
- package/dist/checker/tagged-unions.d.ts +6 -0
- package/dist/checker/tagged-unions.js +31 -1
- package/dist/checker/tags.d.ts +6 -0
- package/dist/checker/tags.js +44 -0
- package/dist/checker/types/constructors.js +10 -1
- package/dist/checker/types/params.js +33 -7
- package/dist/checker/types/printing.js +5 -1
- package/dist/checker/types/relations.js +2 -1
- package/dist/codegen/analysis.d.ts +6 -0
- package/dist/codegen/analysis.js +24 -0
- package/dist/codegen/calls.d.ts +2 -0
- package/dist/codegen/calls.js +23 -1
- package/dist/codegen/component-props.js +8 -1
- package/dist/codegen/control-flow.d.ts +3 -0
- package/dist/codegen/control-flow.js +17 -0
- package/dist/codegen/expressions.d.ts +4 -1
- package/dist/codegen/expressions.js +4 -15
- package/dist/codegen/functions.d.ts +2 -0
- package/dist/codegen/functions.js +13 -0
- package/dist/codegen/mutations.d.ts +6 -0
- package/dist/codegen/mutations.js +36 -0
- package/dist/codegen/options.d.ts +2 -0
- package/dist/codegen/reactive.js +9 -5
- package/dist/codegen/simple-statements.js +3 -1
- package/dist/codegen/statements.d.ts +4 -0
- package/dist/codegen/statements.js +37 -1
- package/dist/codegen/updates.d.ts +6 -1
- package/dist/codegen/updates.js +13 -2
- package/dist/compile.js +2 -0
- package/dist/completion.js +2 -1
- package/dist/declarations.js +18 -0
- package/dist/lexer/base.d.ts +3 -2
- package/dist/lexer/base.js +14 -5
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/token.d.ts +6 -1
- package/dist/lexer/token.js +0 -1
- package/dist/modules.js +4 -3
- package/dist/parser/base.d.ts +5 -0
- package/dist/parser/base.js +9 -1
- package/dist/parser/classes.js +4 -3
- package/dist/parser/declarations.d.ts +2 -0
- package/dist/parser/declarations.js +25 -2
- package/dist/parser/decorators.js +1 -1
- package/dist/parser/expressions.js +6 -0
- package/dist/parser/literals.d.ts +2 -4
- package/dist/parser/literals.js +12 -24
- package/dist/parser/objects.d.ts +8 -0
- package/dist/parser/objects.js +51 -0
- package/dist/parser/params.d.ts +37 -4
- package/dist/parser/params.js +143 -9
- package/dist/parser/postfix.d.ts +5 -0
- package/dist/parser/postfix.js +36 -0
- package/dist/parser/simple-statements.d.ts +2 -0
- package/dist/parser/simple-statements.js +50 -0
- package/dist/parser/statements.js +8 -6
- package/dist/parser/syntax.js +0 -1
- package/dist/parser/types.d.ts +0 -4
- package/dist/parser/types.js +17 -32
- package/dist/project-modules.js +21 -0
- package/dist/reexports.d.ts +4 -0
- package/dist/reexports.js +56 -0
- package/dist/typescript/importer.d.ts +4 -0
- package/dist/typescript/importer.js +27 -3
- package/dist/typescript/library.js +5 -2
- package/dist/walk.js +12 -0
- package/package.json +4 -2
package/dist/parser/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { STATEMENT_KEYWORDS, BRACKETS, ParseError, describe } from "./syntax.js";
|
|
2
2
|
// The parser is a chain of layers, one per area: base → simple statements → statements → control
|
|
3
|
-
// flow → jumps → params → declarations → decorators → classes → types → expressions →
|
|
3
|
+
// flow → jumps → params → declarations → decorators → classes → types → expressions → postfix →
|
|
4
|
+
// objects → literals → markup → Parser
|
|
4
5
|
// (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
|
|
5
6
|
// that earlier ones call are declared here as abstract.
|
|
6
7
|
//
|
|
@@ -43,6 +44,13 @@ export class ParserBase {
|
|
|
43
44
|
check(kind) {
|
|
44
45
|
return this.peek().kind === kind;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* The line ended with the `>` of type arguments, `votes Map<string, number>`: the lexer inserts
|
|
49
|
+
* no semicolon after `>`, which may be an operator, so the line break ends the declaration here.
|
|
50
|
+
*/
|
|
51
|
+
afterTypeArguments() {
|
|
52
|
+
return this.pos > 0 && this.peek(-1).kind === '>' && this.peek().newlineBefore === true;
|
|
53
|
+
}
|
|
46
54
|
/** Whether the current token is the word `word`, e.g. the contextual keyword `from`. */
|
|
47
55
|
checkWord(word) {
|
|
48
56
|
const token = this.peek();
|
package/dist/parser/classes.js
CHANGED
|
@@ -83,7 +83,7 @@ export class ClassParser extends DecoratorParser {
|
|
|
83
83
|
if (modifier)
|
|
84
84
|
this.error(`a constructor cannot be ${word}`, modifier.start, modifier.end);
|
|
85
85
|
}
|
|
86
|
-
const params = this.parseParams(false,
|
|
86
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed', properties: true });
|
|
87
87
|
const resultsStart = this.peek().start;
|
|
88
88
|
if (this.parseResults().length > 0) {
|
|
89
89
|
this.error('constructors cannot have result types', resultsStart, this.span(start).end);
|
|
@@ -161,7 +161,7 @@ export class ClassParser extends DecoratorParser {
|
|
|
161
161
|
if (accessor && asyncToken) {
|
|
162
162
|
this.error(`a ${accessor}ter cannot be async`, asyncToken.start, asyncToken.end);
|
|
163
163
|
}
|
|
164
|
-
const params = this.parseParams(false);
|
|
164
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
165
165
|
const results = this.parseResults();
|
|
166
166
|
const header = this.span(start);
|
|
167
167
|
if (accessor === 'get' && (params.length > 0 || results.length !== 1)) {
|
|
@@ -209,7 +209,8 @@ export class ClassParser extends DecoratorParser {
|
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
211
|
parseTypeAlias(start, exported) {
|
|
212
|
-
|
|
212
|
+
// The word `type`, which the caller has seen.
|
|
213
|
+
this.next();
|
|
213
214
|
const name = this.parseIdentifier('type name');
|
|
214
215
|
const typeParams = this.parseTypeParams();
|
|
215
216
|
const type = this.parseType();
|
|
@@ -6,6 +6,8 @@ export declare abstract class DeclarationParser extends ParamParser {
|
|
|
6
6
|
/** `state items []Item = []` at the top level of a module: state its components share. */
|
|
7
7
|
protected parseDeclaration(start: number, exported: boolean): ast.Statement | undefined;
|
|
8
8
|
protected parseExport(): ast.Statement;
|
|
9
|
+
/** `export { Row, Rows as Table } from "./rows.rk"` or `export * from "./rows.rk"`. */
|
|
10
|
+
private parseExportFrom;
|
|
9
11
|
protected parseFuncDeclaration(start: number, exported: boolean): ast.FuncDeclaration;
|
|
10
12
|
/**
|
|
11
13
|
* `<T any, U Shape>` after a name; `<K, V any>` gives both the constraint, as in Go.
|
|
@@ -65,15 +65,38 @@ export class DeclarationParser extends ParamParser {
|
|
|
65
65
|
if (!this.context.topLevel) {
|
|
66
66
|
this.error('"export" is only allowed at the top level of a module', start, this.span(start).end);
|
|
67
67
|
}
|
|
68
|
+
if (this.check('{') || this.check('*'))
|
|
69
|
+
return this.parseExportFrom(start);
|
|
68
70
|
return (this.parseDeclaration(start, true) ??
|
|
69
71
|
this.fail(`expected a declaration after "export", found ${describe(this.peek())}`));
|
|
70
72
|
}
|
|
73
|
+
/** `export { Row, Rows as Table } from "./rows.rk"` or `export * from "./rows.rk"`. */
|
|
74
|
+
parseExportFrom(start) {
|
|
75
|
+
let specifiers = null;
|
|
76
|
+
if (!this.accept('*')) {
|
|
77
|
+
this.expect('{');
|
|
78
|
+
specifiers = this.parseList('}', () => this.parseImportSpecifier());
|
|
79
|
+
}
|
|
80
|
+
if (!this.checkWord('from')) {
|
|
81
|
+
this.fail('a module exports its own names where they are declared, e.g. export func f(); export { ... } needs from "./module.rk"');
|
|
82
|
+
}
|
|
83
|
+
this.next();
|
|
84
|
+
const source = this.parseStringLiteral('module path');
|
|
85
|
+
const declaration = {
|
|
86
|
+
kind: 'ExportFromDeclaration',
|
|
87
|
+
specifiers,
|
|
88
|
+
source,
|
|
89
|
+
...this.span(start),
|
|
90
|
+
};
|
|
91
|
+
this.endStatement();
|
|
92
|
+
return declaration;
|
|
93
|
+
}
|
|
71
94
|
parseFuncDeclaration(start, exported) {
|
|
72
95
|
const async = this.accept('async');
|
|
73
96
|
this.expect('func');
|
|
74
97
|
const name = this.parseIdentifier('function name');
|
|
75
98
|
const typeParams = this.parseTypeParams();
|
|
76
|
-
const params = this.parseParams(false);
|
|
99
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
77
100
|
const results = this.parseResults();
|
|
78
101
|
const body = this.parseFunctionBody(async);
|
|
79
102
|
return {
|
|
@@ -131,7 +154,7 @@ export class DeclarationParser extends ParamParser {
|
|
|
131
154
|
if (!htmlTag && !/^[A-Z]/.test(name.name)) {
|
|
132
155
|
this.error(`component names start with a capital letter, as in <${name.name.charAt(0).toUpperCase()}${name.name.slice(1)} />`, name.start, name.end);
|
|
133
156
|
}
|
|
134
|
-
const params = this.parseParams(false, true,
|
|
157
|
+
const params = this.parseParams(false, { defaults: true, rest: 'attrs' });
|
|
135
158
|
const body = this.withContext({ ...FUNCTION_BODY, inComponent: true }, () => this.parseComponentBody());
|
|
136
159
|
return {
|
|
137
160
|
kind: 'ComponentDeclaration',
|
|
@@ -15,7 +15,7 @@ export class DecoratorParser extends DeclarationParser {
|
|
|
15
15
|
this.error('decorators can only be declared at the top level of a module', keyword.start, keyword.end);
|
|
16
16
|
}
|
|
17
17
|
const name = this.parseIdentifier('decorator name');
|
|
18
|
-
const params = this.parseParams(false, true);
|
|
18
|
+
const params = this.parseParams(false, { defaults: true });
|
|
19
19
|
const needs = this.parseNeeds();
|
|
20
20
|
const includes = this.parseIncludes();
|
|
21
21
|
const body = this.withContext({ ...FUNCTION_BODY, inComponent: true }, () => this.parseDecoratorBody());
|
|
@@ -35,6 +35,12 @@ export class ExpressionParser extends TypeParser {
|
|
|
35
35
|
const precedence = BINARY_PRECEDENCE[operator];
|
|
36
36
|
if (precedence < minPrecedence)
|
|
37
37
|
return left;
|
|
38
|
+
// `await load<number>(1)` reads `await load` first.
|
|
39
|
+
const callee = left.kind === 'UnaryExpression' && left.operator === 'await' ? left.argument : left;
|
|
40
|
+
if (operator === '<' &&
|
|
41
|
+
(callee.kind === 'Identifier' || callee.kind === 'MemberExpression')) {
|
|
42
|
+
this.rejectTypeArguments();
|
|
43
|
+
}
|
|
38
44
|
this.next();
|
|
39
45
|
// `**` is right-associative, all other binary operators are left-associative.
|
|
40
46
|
const right = this.parseBinary(operator === '**' ? precedence : precedence + 1);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as ast from '../ast.ts';
|
|
2
2
|
import { type StringToken, type TokenKind } from '../lexer/token.ts';
|
|
3
|
-
import {
|
|
4
|
-
export declare abstract class LiteralParser extends
|
|
3
|
+
import { ObjectParser } from './objects.ts';
|
|
4
|
+
export declare abstract class LiteralParser extends ObjectParser {
|
|
5
5
|
/** Whether the `(` at the current position opens arrow function parameters: `(...) =>`. */
|
|
6
6
|
protected isArrowFunctionAhead(): boolean;
|
|
7
7
|
protected parseArrowFunction(async?: boolean): ast.ArrowFunction;
|
|
@@ -32,8 +32,6 @@ export declare abstract class LiteralParser extends ExpressionParser {
|
|
|
32
32
|
protected parseStreamChan(start: number): ast.StreamChanExpression;
|
|
33
33
|
/** `[]byte("text")`: a type, then its value in parentheses. */
|
|
34
34
|
protected parseConversion(): ast.ConversionExpression;
|
|
35
|
-
protected parseObjectLiteral(): ast.ObjectLiteral;
|
|
36
|
-
protected parseProperty(): ast.Property | ast.SpreadElement;
|
|
37
35
|
protected parseTemplate(first: StringToken): ast.TemplateLiteral;
|
|
38
36
|
/** Parses `item, item, ...` up to `close`, which is consumed. A trailing comma is allowed. */
|
|
39
37
|
protected parseList<T>(close: TokenKind, parseItem: () => T): T[];
|
package/dist/parser/literals.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isKeyword } from "../lexer/token.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ObjectParser } from "./objects.js";
|
|
3
3
|
import { FUNCTION_BODY, ParseError, describe, templateElement } from "./syntax.js";
|
|
4
4
|
// Functions, `new`, literals of arrays, objects and templates, names.
|
|
5
|
-
export class LiteralParser extends
|
|
5
|
+
export class LiteralParser extends ObjectParser {
|
|
6
6
|
/** Whether the `(` at the current position opens arrow function parameters: `(...) =>`. */
|
|
7
7
|
isArrowFunctionAhead() {
|
|
8
8
|
let depth = 0;
|
|
@@ -39,7 +39,7 @@ export class LiteralParser extends ExpressionParser {
|
|
|
39
39
|
];
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
|
-
params = this.parseParams(true);
|
|
42
|
+
params = this.parseParams(true, { defaults: true, rest: 'typed' });
|
|
43
43
|
}
|
|
44
44
|
this.expect('=>');
|
|
45
45
|
const body = this.withContext({ ...FUNCTION_BODY, inAsync: async }, () => this.check('{') ? this.parseBlock() : this.parseExpression());
|
|
@@ -60,7 +60,7 @@ export class LiteralParser extends ExpressionParser {
|
|
|
60
60
|
}
|
|
61
61
|
parseFuncExpression(async = false) {
|
|
62
62
|
const start = this.expect('func').start;
|
|
63
|
-
const params = this.parseParams(false);
|
|
63
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
64
64
|
const results = this.parseResults();
|
|
65
65
|
const body = this.parseFunctionBody(async);
|
|
66
66
|
return {
|
|
@@ -86,6 +86,9 @@ export class LiteralParser extends ExpressionParser {
|
|
|
86
86
|
...this.span(callee.start),
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
+
// `new Promise<void>(...)`: the type arguments are inferred, as in calls.
|
|
90
|
+
if (this.check('<'))
|
|
91
|
+
this.rejectTypeArguments();
|
|
89
92
|
const args = this.check('(') ? this.parseArguments() : [];
|
|
90
93
|
return { kind: 'NewExpression', callee, arguments: args, ...this.span(start) };
|
|
91
94
|
}
|
|
@@ -220,26 +223,6 @@ export class LiteralParser extends ExpressionParser {
|
|
|
220
223
|
this.expect(')');
|
|
221
224
|
return { kind: 'ConversionExpression', type, argument, ...this.span(start) };
|
|
222
225
|
}
|
|
223
|
-
parseObjectLiteral() {
|
|
224
|
-
const start = this.expect('{').start;
|
|
225
|
-
const properties = this.nested(() => this.parseList('}', () => this.parseProperty()));
|
|
226
|
-
return { kind: 'ObjectLiteral', properties, ...this.span(start) };
|
|
227
|
-
}
|
|
228
|
-
parseProperty() {
|
|
229
|
-
const token = this.peek();
|
|
230
|
-
if (token.kind === '...')
|
|
231
|
-
return this.parseElement();
|
|
232
|
-
const key = token.kind === 'String' ? this.parseStringLiteral() : this.parsePropertyName();
|
|
233
|
-
if (this.accept(':')) {
|
|
234
|
-
const value = this.parseExpression();
|
|
235
|
-
return { kind: 'Property', key, value, shorthand: false, ...this.span(token.start) };
|
|
236
|
-
}
|
|
237
|
-
// Shorthand `{ name }` means `{ name: name }`, so the key must be a plain name.
|
|
238
|
-
if (key.kind !== 'Identifier' || token.kind !== 'Identifier') {
|
|
239
|
-
this.fail(`expected ":" after property name, found ${describe(this.peek())}`);
|
|
240
|
-
}
|
|
241
|
-
return { kind: 'Property', key, value: { ...key }, shorthand: true, ...this.span(token.start) };
|
|
242
|
-
}
|
|
243
226
|
parseTemplate(first) {
|
|
244
227
|
this.next();
|
|
245
228
|
const quasis = [templateElement(first)];
|
|
@@ -248,6 +231,11 @@ export class LiteralParser extends ExpressionParser {
|
|
|
248
231
|
while (true) {
|
|
249
232
|
expressions.push(this.nested(() => this.parseExpression()));
|
|
250
233
|
const part = this.peek();
|
|
234
|
+
if ((part.kind === '++' || part.kind === '--') &&
|
|
235
|
+
expressions.at(-1)?.kind === 'Identifier') {
|
|
236
|
+
const name = expressions.at(-1).name;
|
|
237
|
+
this.fail(`"${part.kind}" is a statement and cannot be used inside a template: use ${name} here and write ${name}${part.kind} on a line of its own`);
|
|
238
|
+
}
|
|
251
239
|
if (part.kind !== 'TemplateMiddle' && part.kind !== 'TemplateTail') {
|
|
252
240
|
this.fail(`expected "}" to close the template substitution, found ${describe(part)}`);
|
|
253
241
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type * as ast from '../ast.ts';
|
|
2
|
+
import { PostfixParser } from './postfix.ts';
|
|
3
|
+
export declare abstract class ObjectParser extends PostfixParser {
|
|
4
|
+
protected parseObjectLiteral(): ast.ObjectLiteral;
|
|
5
|
+
protected parseProperty(): ast.Property | ast.SpreadElement;
|
|
6
|
+
/** `fetch(req Request) Response { ... }`: typed parameters, as those of a `func` literal. */
|
|
7
|
+
private parseObjectMethod;
|
|
8
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PostfixParser } from "./postfix.js";
|
|
2
|
+
import { describe } from "./syntax.js";
|
|
3
|
+
// Object literals: `{ name: value }`, `{ name }`, `{ ...other }`, and methods written as in a
|
|
4
|
+
// class, `{ fetch(req Request) Response { ... } }`.
|
|
5
|
+
export class ObjectParser extends PostfixParser {
|
|
6
|
+
parseObjectLiteral() {
|
|
7
|
+
const start = this.expect('{').start;
|
|
8
|
+
const properties = this.nested(() => this.parseList('}', () => this.parseProperty()));
|
|
9
|
+
return { kind: 'ObjectLiteral', properties, ...this.span(start) };
|
|
10
|
+
}
|
|
11
|
+
parseProperty() {
|
|
12
|
+
const token = this.peek();
|
|
13
|
+
if (token.kind === '...') {
|
|
14
|
+
this.next();
|
|
15
|
+
const argument = this.parseExpression();
|
|
16
|
+
return { kind: 'SpreadElement', argument, ...this.span(token.start) };
|
|
17
|
+
}
|
|
18
|
+
// `async load() T { ... }`: `async` before the name of a method.
|
|
19
|
+
const async = token.kind === 'async' && this.peek(2).kind === '(';
|
|
20
|
+
if (async)
|
|
21
|
+
this.next();
|
|
22
|
+
const key = this.check('String') ? this.parseStringLiteral() : this.parsePropertyName();
|
|
23
|
+
if (this.check('('))
|
|
24
|
+
return this.parseObjectMethod(key, token.start, async);
|
|
25
|
+
if (this.accept(':')) {
|
|
26
|
+
const value = this.parseExpression();
|
|
27
|
+
return { kind: 'Property', key, value, shorthand: false, ...this.span(token.start) };
|
|
28
|
+
}
|
|
29
|
+
// Shorthand `{ name }` means `{ name: name }`, so the key must be a plain name.
|
|
30
|
+
if (key.kind !== 'Identifier' || token.kind !== 'Identifier') {
|
|
31
|
+
this.fail(`expected ":" after property name, found ${describe(this.peek())}`);
|
|
32
|
+
}
|
|
33
|
+
return { kind: 'Property', key, value: { ...key }, shorthand: true, ...this.span(token.start) };
|
|
34
|
+
}
|
|
35
|
+
/** `fetch(req Request) Response { ... }`: typed parameters, as those of a `func` literal. */
|
|
36
|
+
parseObjectMethod(key, start, async) {
|
|
37
|
+
const funcStart = this.peek().start;
|
|
38
|
+
const params = this.parseParams(false, { defaults: true, rest: 'typed' });
|
|
39
|
+
const results = this.parseResults();
|
|
40
|
+
const body = this.parseFunctionBody(async);
|
|
41
|
+
const value = {
|
|
42
|
+
kind: 'FuncExpression',
|
|
43
|
+
...(async ? { async } : {}),
|
|
44
|
+
params,
|
|
45
|
+
results,
|
|
46
|
+
body,
|
|
47
|
+
...this.span(funcStart),
|
|
48
|
+
};
|
|
49
|
+
return { kind: 'Property', key, value, shorthand: false, method: true, ...this.span(start) };
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/parser/params.d.ts
CHANGED
|
@@ -4,9 +4,14 @@ export declare abstract class ParamParser extends JumpParser {
|
|
|
4
4
|
/**
|
|
5
5
|
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
6
6
|
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
7
|
-
* properties: `(private name string)`.
|
|
7
|
+
* properties: `(private name string)`. Functions take `...parts []any` last, and components
|
|
8
|
+
* `...attrs`, the attributes of an element; both may have default values, `times = 1`.
|
|
8
9
|
*/
|
|
9
|
-
protected parseParams(typesOptional: boolean,
|
|
10
|
+
protected parseParams(typesOptional: boolean, options?: {
|
|
11
|
+
defaults?: boolean;
|
|
12
|
+
rest?: 'typed' | 'attrs';
|
|
13
|
+
properties?: boolean;
|
|
14
|
+
}): ast.Parameter[];
|
|
10
15
|
/** `private`, `readonly` or both before a parameter that is a property of its class. */
|
|
11
16
|
private parsePropertyModifiers;
|
|
12
17
|
/**
|
|
@@ -14,8 +19,36 @@ export declare abstract class ParamParser extends JumpParser {
|
|
|
14
19
|
* not by `,` or `)`, so a parameter named bind, `(bind Mode)`, stays one.
|
|
15
20
|
*/
|
|
16
21
|
private parseBindModifier;
|
|
22
|
+
/** `...parts []any`: the arguments after the others, in an array, as in JS. */
|
|
23
|
+
private parseTypedRest;
|
|
17
24
|
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
18
25
|
private parseRestParam;
|
|
19
|
-
/**
|
|
20
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Result types after a parameter list: nothing, `T`, or `(T1, T2)`. A `{` may start the body
|
|
28
|
+
* instead: it starts an object type when the body follows that type,
|
|
29
|
+
* `func names() { [string]: string } { ... }`, or a value does, `f func() { [string]: T } = g`.
|
|
30
|
+
* A method of an interface has no body, so there `{` is always a type.
|
|
31
|
+
*/
|
|
32
|
+
protected parseResults(method?: boolean): ast.TypeNode[];
|
|
33
|
+
/** Whether the `{ ... }` here is followed by a body, `{`, or by a value, `=`. */
|
|
34
|
+
private objectTypeBeforeBody;
|
|
35
|
+
/**
|
|
36
|
+
* The type of a parameter of a function type, after its name if it has one, kept in `names`;
|
|
37
|
+
* `...` before the rest parameter is kept there too, `...parts`.
|
|
38
|
+
*/
|
|
39
|
+
protected parseParamType(names: string[]): ast.TypeNode;
|
|
40
|
+
/** `func(string, ...[]any)`: the last parameter, taken out of `params`, if it is the rest. */
|
|
41
|
+
protected restOf(params: ast.TypeNode[], names: string[]): ast.TypeNode | null;
|
|
42
|
+
/**
|
|
43
|
+
* `func(a, b number)`: as in Go, if a parameter has a name, all have, so `a` alone is a name that
|
|
44
|
+
* shares the type of the next one. Without names, `func(A, B)` is a list of types.
|
|
45
|
+
*/
|
|
46
|
+
protected groupParams(params: ast.TypeNode[], names: string[]): ast.TypeNode[];
|
|
47
|
+
/** `paths []string` in `(paths []string) => Shape`: a name before the type of a parameter. */
|
|
48
|
+
private startsNamedParam;
|
|
49
|
+
/**
|
|
50
|
+
* `load<number>(1)` would be two comparisons, `(load < number) > (1)`: a call takes no type
|
|
51
|
+
* arguments, which are inferred, so the likely mistake is reported as one.
|
|
52
|
+
*/
|
|
53
|
+
protected rejectTypeArguments(): void;
|
|
21
54
|
}
|
package/dist/parser/params.js
CHANGED
|
@@ -6,15 +6,20 @@ export class ParamParser extends JumpParser {
|
|
|
6
6
|
/**
|
|
7
7
|
* `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
|
|
8
8
|
* Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
|
|
9
|
-
* properties: `(private name string)`.
|
|
9
|
+
* properties: `(private name string)`. Functions take `...parts []any` last, and components
|
|
10
|
+
* `...attrs`, the attributes of an element; both may have default values, `times = 1`.
|
|
10
11
|
*/
|
|
11
|
-
parseParams(typesOptional,
|
|
12
|
+
parseParams(typesOptional, options = {}) {
|
|
13
|
+
const { defaults: allowDefaults = false, properties: allowProperties = false } = options;
|
|
14
|
+
const allowRest = options.rest === 'attrs';
|
|
12
15
|
this.expect('(');
|
|
13
16
|
const params = [];
|
|
14
17
|
let untyped = 0;
|
|
15
18
|
while (!this.check(')')) {
|
|
16
19
|
if (this.check('...')) {
|
|
17
|
-
params.push(
|
|
20
|
+
params.push(options.rest === 'typed'
|
|
21
|
+
? this.parseTypedRest(typesOptional)
|
|
22
|
+
: this.parseRestParam(allowRest));
|
|
18
23
|
break;
|
|
19
24
|
}
|
|
20
25
|
const paramStart = this.peek().start;
|
|
@@ -35,7 +40,11 @@ export class ParamParser extends JumpParser {
|
|
|
35
40
|
...(bind ? { bind: true } : {}),
|
|
36
41
|
...this.span(paramStart),
|
|
37
42
|
});
|
|
38
|
-
|
|
43
|
+
// `times = 1` has the type of its value.
|
|
44
|
+
if (type === null && defaultValue !== null) {
|
|
45
|
+
untyped = 0;
|
|
46
|
+
}
|
|
47
|
+
else if (type === null) {
|
|
39
48
|
untyped++;
|
|
40
49
|
}
|
|
41
50
|
else {
|
|
@@ -100,12 +109,30 @@ export class ParamParser extends JumpParser {
|
|
|
100
109
|
}
|
|
101
110
|
return true;
|
|
102
111
|
}
|
|
112
|
+
/** `...parts []any`: the arguments after the others, in an array, as in JS. */
|
|
113
|
+
parseTypedRest(typesOptional) {
|
|
114
|
+
const start = this.next().start;
|
|
115
|
+
const name = this.parseIdentifier('parameter name');
|
|
116
|
+
const type = this.canStartType() ? this.parseType() : null;
|
|
117
|
+
if (type === null && !typesOptional) {
|
|
118
|
+
this.error(`...${name.name} needs the type of its array, e.g. ...${name.name} []any`, start, name.end);
|
|
119
|
+
}
|
|
120
|
+
// After the error, the rest of the list is skipped up to the `)`.
|
|
121
|
+
if (this.check(',')) {
|
|
122
|
+
this.error(`...${name.name} is the last parameter`, start, name.end);
|
|
123
|
+
while (!this.check(')') && !this.check('EOF'))
|
|
124
|
+
this.next();
|
|
125
|
+
}
|
|
126
|
+
if (this.check('='))
|
|
127
|
+
this.error(`...${name.name} has no default value: it is [] without arguments`, start, name.end);
|
|
128
|
+
return { kind: 'Parameter', name, type, defaultValue: null, rest: true, ...this.span(start) };
|
|
129
|
+
}
|
|
103
130
|
/** `...attrs`: the attributes that a component passes on to one of its elements. */
|
|
104
131
|
parseRestParam(allowed) {
|
|
105
132
|
const start = this.next().start;
|
|
106
133
|
const name = this.parseIdentifier('parameter name');
|
|
107
134
|
if (!allowed) {
|
|
108
|
-
this.error(`...${name.name}
|
|
135
|
+
this.error(`...${name.name} needs the type of its array, e.g. ...${name.name} []any`, start, name.end);
|
|
109
136
|
}
|
|
110
137
|
if (this.canStartType() || this.check('=')) {
|
|
111
138
|
const at = this.peek().start;
|
|
@@ -131,13 +158,120 @@ export class ParamParser extends JumpParser {
|
|
|
131
158
|
...this.span(start),
|
|
132
159
|
};
|
|
133
160
|
}
|
|
134
|
-
/**
|
|
135
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Result types after a parameter list: nothing, `T`, or `(T1, T2)`. A `{` may start the body
|
|
163
|
+
* instead: it starts an object type when the body follows that type,
|
|
164
|
+
* `func names() { [string]: string } { ... }`, or a value does, `f func() { [string]: T } = g`.
|
|
165
|
+
* A method of an interface has no body, so there `{` is always a type.
|
|
166
|
+
*/
|
|
167
|
+
parseResults(method = false) {
|
|
136
168
|
if (this.accept('('))
|
|
137
169
|
return this.parseList(')', () => this.parseType());
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
if (this.check('{') && !method && !this.objectTypeBeforeBody())
|
|
171
|
+
return [];
|
|
172
|
+
if (this.canStartType())
|
|
140
173
|
return [this.parseType()];
|
|
141
174
|
return [];
|
|
142
175
|
}
|
|
176
|
+
/** Whether the `{ ... }` here is followed by a body, `{`, or by a value, `=`. */
|
|
177
|
+
objectTypeBeforeBody() {
|
|
178
|
+
let depth = 0;
|
|
179
|
+
for (let offset = 0;; offset++) {
|
|
180
|
+
const { kind } = this.peek(offset);
|
|
181
|
+
if (kind === 'EOF')
|
|
182
|
+
return false;
|
|
183
|
+
if (kind === '{')
|
|
184
|
+
depth++;
|
|
185
|
+
else if (kind === '}' && --depth === 0) {
|
|
186
|
+
const next = this.peek(offset + 1).kind;
|
|
187
|
+
return next === '{' || next === '=';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The type of a parameter of a function type, after its name if it has one, kept in `names`;
|
|
193
|
+
* `...` before the rest parameter is kept there too, `...parts`.
|
|
194
|
+
*/
|
|
195
|
+
parseParamType(names) {
|
|
196
|
+
const rest = this.accept('...') ? '...' : '';
|
|
197
|
+
names.push(rest + (this.check('Identifier') && this.startsNamedParam() ? this.next().text : ''));
|
|
198
|
+
return this.parseType();
|
|
199
|
+
}
|
|
200
|
+
/** `func(string, ...[]any)`: the last parameter, taken out of `params`, if it is the rest. */
|
|
201
|
+
restOf(params, names) {
|
|
202
|
+
const at = names.findIndex((name) => name.startsWith('...'));
|
|
203
|
+
if (at === -1)
|
|
204
|
+
return null;
|
|
205
|
+
if (at !== params.length - 1) {
|
|
206
|
+
this.error('the rest parameter is the last one, e.g. func(string, ...[]any)', params[at].start, params[at].end);
|
|
207
|
+
}
|
|
208
|
+
names.splice(at, 1);
|
|
209
|
+
return params.splice(at, 1)[0];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* `func(a, b number)`: as in Go, if a parameter has a name, all have, so `a` alone is a name that
|
|
213
|
+
* shares the type of the next one. Without names, `func(A, B)` is a list of types.
|
|
214
|
+
*/
|
|
215
|
+
groupParams(params, names) {
|
|
216
|
+
// `...` alone, an unnamed rest parameter, gives no name.
|
|
217
|
+
const named = (name) => name.replace(/^\.\.\./, '') !== '';
|
|
218
|
+
if (!names.some(named))
|
|
219
|
+
return params;
|
|
220
|
+
return params.map((param, i) => {
|
|
221
|
+
if (names[i] !== '' || param.kind !== 'TypeReference' || param.typeArgs.length > 0) {
|
|
222
|
+
return param;
|
|
223
|
+
}
|
|
224
|
+
const shared = names.findIndex((name, j) => j > i && named(name));
|
|
225
|
+
if (shared === -1) {
|
|
226
|
+
this.error(`parameter "${param.name.name}" has no type: write it after the name, e.g. func(a, b number)`, param.start, param.end);
|
|
227
|
+
return param;
|
|
228
|
+
}
|
|
229
|
+
names[i] = param.name.name;
|
|
230
|
+
return params[shared];
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** `paths []string` in `(paths []string) => Shape`: a name before the type of a parameter. */
|
|
234
|
+
startsNamedParam() {
|
|
235
|
+
const next = this.peek(1);
|
|
236
|
+
switch (next.kind) {
|
|
237
|
+
case 'Identifier':
|
|
238
|
+
case '?':
|
|
239
|
+
case '{':
|
|
240
|
+
case '(':
|
|
241
|
+
case 'func':
|
|
242
|
+
case 'chan':
|
|
243
|
+
case '<-':
|
|
244
|
+
case 'String':
|
|
245
|
+
case 'Number':
|
|
246
|
+
return true;
|
|
247
|
+
// `x []number` is named.
|
|
248
|
+
case '[':
|
|
249
|
+
return this.peek(2).kind === ']';
|
|
250
|
+
default:
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* `load<number>(1)` would be two comparisons, `(load < number) > (1)`: a call takes no type
|
|
256
|
+
* arguments, which are inferred, so the likely mistake is reported as one.
|
|
257
|
+
*/
|
|
258
|
+
rejectTypeArguments() {
|
|
259
|
+
const TYPE_TOKENS = new Set(['Identifier', ',', '[', ']', '?', '.', '|', 'chan']);
|
|
260
|
+
let depth = 0;
|
|
261
|
+
for (let offset = 0;; offset++) {
|
|
262
|
+
const { kind } = this.peek(offset);
|
|
263
|
+
if (kind === '<')
|
|
264
|
+
depth++;
|
|
265
|
+
else if (kind === '>')
|
|
266
|
+
depth--;
|
|
267
|
+
else if (!TYPE_TOKENS.has(kind))
|
|
268
|
+
return;
|
|
269
|
+
if (depth === 0) {
|
|
270
|
+
if (this.peek(offset + 1).kind !== '(')
|
|
271
|
+
return;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.fail('a call takes no type arguments: they are inferred from the arguments and the expected type; give the variable a type instead, e.g. const done Promise<void> = new Promise(...)');
|
|
276
|
+
}
|
|
143
277
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ExpressionParser } from "./expressions.js";
|
|
2
|
+
// What may follow a value besides members, indexes and calls: `user!`, which takes it not to be
|
|
3
|
+
// null, and a template right after it, sql`select ... ${id}`, which calls it with its parts.
|
|
4
|
+
export class PostfixParser extends ExpressionParser {
|
|
5
|
+
parsePostfix(expression) {
|
|
6
|
+
let result = super.parsePostfix(expression);
|
|
7
|
+
while (true) {
|
|
8
|
+
const token = this.peek();
|
|
9
|
+
// `user!.name`: the `!` right after the value, so that `!` before a value stays a negation.
|
|
10
|
+
if (token.kind === '!' && token.start === result.end) {
|
|
11
|
+
this.next();
|
|
12
|
+
result = {
|
|
13
|
+
kind: 'NonNullExpression',
|
|
14
|
+
expression: result,
|
|
15
|
+
start: result.start,
|
|
16
|
+
end: token.end,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
else if ((token.kind === 'Template' || token.kind === 'TemplateHead') &&
|
|
20
|
+
!token.newlineBefore) {
|
|
21
|
+
const quasi = this.parseTemplate(token);
|
|
22
|
+
result = {
|
|
23
|
+
kind: 'TaggedTemplateExpression',
|
|
24
|
+
tag: result,
|
|
25
|
+
quasi,
|
|
26
|
+
start: result.start,
|
|
27
|
+
end: quasi.end,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
result = super.parsePostfix(result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -19,5 +19,7 @@ export declare abstract class SimpleStatementParser extends ParserBase {
|
|
|
19
19
|
/** `ref input` is one name, and the markup gives its element. */
|
|
20
20
|
private checkRef;
|
|
21
21
|
private parseDerivedBlock;
|
|
22
|
+
/** `const { name, size: bytes, ...rest } = file`: fields of an object, one level deep. */
|
|
23
|
+
private parseDestructuring;
|
|
22
24
|
protected parseVariableDeclaration(start: number, exported: boolean): ast.VariableDeclaration;
|
|
23
25
|
}
|