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.
Files changed (104) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/CHANGELOG.ru.md +235 -0
  3. package/dist/aliases.d.ts +2 -0
  4. package/dist/aliases.js +83 -0
  5. package/dist/ast/declarations.d.ts +18 -0
  6. package/dist/ast/expressions.d.ts +14 -1
  7. package/dist/ast/markup.d.ts +2 -0
  8. package/dist/ast/statements.d.ts +2 -2
  9. package/dist/checker/assignments.js +2 -1
  10. package/dist/checker/base.d.ts +3 -0
  11. package/dist/checker/base.js +1 -1
  12. package/dist/checker/builtins.js +5 -1
  13. package/dist/checker/calls.d.ts +2 -0
  14. package/dist/checker/calls.js +18 -3
  15. package/dist/checker/classes.js +8 -0
  16. package/dist/checker/component-bindings.d.ts +16 -0
  17. package/dist/checker/component-bindings.js +65 -2
  18. package/dist/checker/context.d.ts +12 -0
  19. package/dist/checker/declarations.d.ts +2 -0
  20. package/dist/checker/declarations.js +38 -3
  21. package/dist/checker/expressions.d.ts +1 -0
  22. package/dist/checker/expressions.js +17 -1
  23. package/dist/checker/flow.d.ts +1 -1
  24. package/dist/checker/flow.js +10 -0
  25. package/dist/checker/functions.d.ts +2 -0
  26. package/dist/checker/functions.js +33 -7
  27. package/dist/checker/goroutines.d.ts +2 -2
  28. package/dist/checker/goroutines.js +2 -2
  29. package/dist/checker/imports.d.ts +10 -0
  30. package/dist/checker/imports.js +73 -6
  31. package/dist/checker/initializers.d.ts +12 -0
  32. package/dist/checker/initializers.js +80 -0
  33. package/dist/checker/loops.js +3 -1
  34. package/dist/checker/members.js +1 -0
  35. package/dist/checker/resolution.d.ts +2 -0
  36. package/dist/checker/resolution.js +13 -2
  37. package/dist/checker/statements.d.ts +5 -0
  38. package/dist/checker/statements.js +26 -3
  39. package/dist/checker/tagged-unions.d.ts +6 -0
  40. package/dist/checker/tagged-unions.js +31 -1
  41. package/dist/checker/tags.d.ts +6 -0
  42. package/dist/checker/tags.js +44 -0
  43. package/dist/checker/types/constructors.js +10 -1
  44. package/dist/checker/types/params.js +33 -7
  45. package/dist/checker/types/printing.js +5 -1
  46. package/dist/checker/types/relations.js +2 -1
  47. package/dist/codegen/analysis.d.ts +6 -0
  48. package/dist/codegen/analysis.js +24 -0
  49. package/dist/codegen/calls.d.ts +2 -0
  50. package/dist/codegen/calls.js +23 -1
  51. package/dist/codegen/component-props.js +8 -1
  52. package/dist/codegen/control-flow.d.ts +3 -0
  53. package/dist/codegen/control-flow.js +17 -0
  54. package/dist/codegen/expressions.d.ts +4 -1
  55. package/dist/codegen/expressions.js +4 -15
  56. package/dist/codegen/functions.d.ts +2 -0
  57. package/dist/codegen/functions.js +13 -0
  58. package/dist/codegen/mutations.d.ts +6 -0
  59. package/dist/codegen/mutations.js +36 -0
  60. package/dist/codegen/options.d.ts +2 -0
  61. package/dist/codegen/reactive.js +9 -5
  62. package/dist/codegen/simple-statements.js +3 -1
  63. package/dist/codegen/statements.d.ts +4 -0
  64. package/dist/codegen/statements.js +37 -1
  65. package/dist/codegen/updates.d.ts +6 -1
  66. package/dist/codegen/updates.js +13 -2
  67. package/dist/compile.js +2 -0
  68. package/dist/completion.js +2 -1
  69. package/dist/declarations.js +18 -0
  70. package/dist/lexer/base.d.ts +3 -2
  71. package/dist/lexer/base.js +14 -5
  72. package/dist/lexer/lexer.js +3 -0
  73. package/dist/lexer/token.d.ts +6 -1
  74. package/dist/lexer/token.js +0 -1
  75. package/dist/modules.js +4 -3
  76. package/dist/parser/base.d.ts +5 -0
  77. package/dist/parser/base.js +9 -1
  78. package/dist/parser/classes.js +4 -3
  79. package/dist/parser/declarations.d.ts +2 -0
  80. package/dist/parser/declarations.js +25 -2
  81. package/dist/parser/decorators.js +1 -1
  82. package/dist/parser/expressions.js +6 -0
  83. package/dist/parser/literals.d.ts +2 -4
  84. package/dist/parser/literals.js +12 -24
  85. package/dist/parser/objects.d.ts +8 -0
  86. package/dist/parser/objects.js +51 -0
  87. package/dist/parser/params.d.ts +37 -4
  88. package/dist/parser/params.js +143 -9
  89. package/dist/parser/postfix.d.ts +5 -0
  90. package/dist/parser/postfix.js +36 -0
  91. package/dist/parser/simple-statements.d.ts +2 -0
  92. package/dist/parser/simple-statements.js +50 -0
  93. package/dist/parser/statements.js +8 -6
  94. package/dist/parser/syntax.js +0 -1
  95. package/dist/parser/types.d.ts +0 -4
  96. package/dist/parser/types.js +17 -32
  97. package/dist/project-modules.js +21 -0
  98. package/dist/reexports.d.ts +4 -0
  99. package/dist/reexports.js +56 -0
  100. package/dist/typescript/importer.d.ts +4 -0
  101. package/dist/typescript/importer.js +27 -3
  102. package/dist/typescript/library.js +5 -2
  103. package/dist/walk.js +12 -0
  104. package/package.json +4 -2
@@ -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 → literals → markup → Parser
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();
@@ -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, false, false, true);
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
- this.expect('type');
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, 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 { ExpressionParser } from './expressions.ts';
4
- export declare abstract class LiteralParser extends ExpressionParser {
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[];
@@ -1,8 +1,8 @@
1
1
  import { isKeyword } from "../lexer/token.js";
2
- import { ExpressionParser } from "./expressions.js";
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 ExpressionParser {
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
+ }
@@ -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, allowDefaults?: boolean, allowRest?: boolean, allowProperties?: boolean): ast.Parameter[];
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
- /** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
20
- protected parseResults(): ast.TypeNode[];
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
  }
@@ -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, allowDefaults = false, allowRest = false, allowProperties = false) {
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(this.parseRestParam(allowRest));
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
- if (type === null) {
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} is only for components: it takes the attributes of an element`, start, name.end);
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
- /** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
135
- parseResults() {
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
- // `{` after the parameters starts the body, so an object result type needs parentheses.
139
- if (this.canStartType() && !this.check('{'))
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,5 @@
1
+ import type * as ast from '../ast.ts';
2
+ import { ExpressionParser } from './expressions.ts';
3
+ export declare abstract class PostfixParser extends ExpressionParser {
4
+ protected parsePostfix(expression: ast.Expression): ast.Expression;
5
+ }
@@ -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
  }