rooklang 0.3.4 → 0.4.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 (123) hide show
  1. package/README.md +2 -0
  2. package/README.ru.md +2 -0
  3. package/dist/ast/declarations.d.ts +19 -3
  4. package/dist/ast/expressions.d.ts +4 -8
  5. package/dist/ast/statements.d.ts +3 -1
  6. package/dist/checker/assignments.d.ts +2 -0
  7. package/dist/checker/assignments.js +9 -3
  8. package/dist/checker/base.d.ts +8 -2
  9. package/dist/checker/base.js +3 -3
  10. package/dist/checker/builtins.js +7 -3
  11. package/dist/checker/channels.js +1 -2
  12. package/dist/checker/classes.js +7 -1
  13. package/dist/checker/closing.js +1 -1
  14. package/dist/checker/completion.d.ts +1 -1
  15. package/dist/checker/completion.js +2 -1
  16. package/dist/checker/component-bindings.d.ts +7 -0
  17. package/dist/checker/component-bindings.js +45 -0
  18. package/dist/checker/component-values.js +10 -7
  19. package/dist/checker/components.d.ts +2 -2
  20. package/dist/checker/components.js +8 -3
  21. package/dist/checker/context.d.ts +28 -5
  22. package/dist/checker/context.js +23 -0
  23. package/dist/checker/control-flow.d.ts +6 -5
  24. package/dist/checker/control-flow.js +48 -40
  25. package/dist/checker/declarations.d.ts +2 -2
  26. package/dist/checker/declarations.js +3 -2
  27. package/dist/checker/decorator-exports.js +4 -0
  28. package/dist/checker/decorator-includes.d.ts +9 -0
  29. package/dist/checker/decorator-includes.js +71 -0
  30. package/dist/checker/decorator-members.d.ts +8 -10
  31. package/dist/checker/decorator-members.js +28 -31
  32. package/dist/checker/decorator-wrappers.d.ts +1 -1
  33. package/dist/checker/decorator-wrappers.js +3 -2
  34. package/dist/checker/decorators.d.ts +14 -5
  35. package/dist/checker/decorators.js +52 -25
  36. package/dist/checker/expressions.d.ts +1 -1
  37. package/dist/checker/expressions.js +7 -6
  38. package/dist/checker/flow.d.ts +25 -3
  39. package/dist/checker/flow.js +48 -8
  40. package/dist/checker/functions.d.ts +2 -2
  41. package/dist/checker/functions.js +9 -5
  42. package/dist/checker/helpers.d.ts +4 -1
  43. package/dist/checker/helpers.js +7 -2
  44. package/dist/checker/imports.js +5 -2
  45. package/dist/checker/loops.d.ts +2 -0
  46. package/dist/checker/loops.js +27 -2
  47. package/dist/checker/markup.js +14 -2
  48. package/dist/checker/members.js +3 -1
  49. package/dist/checker/shared-state.d.ts +11 -0
  50. package/dist/checker/shared-state.js +37 -0
  51. package/dist/checker/statements.d.ts +5 -2
  52. package/dist/checker/statements.js +51 -22
  53. package/dist/checker/tagged-unions.d.ts +37 -0
  54. package/dist/checker/tagged-unions.js +132 -0
  55. package/dist/checker/untyped-globals.d.ts +1 -0
  56. package/dist/checker/untyped-globals.js +3 -1
  57. package/dist/codegen/attributes.d.ts +4 -0
  58. package/dist/codegen/attributes.js +24 -2
  59. package/dist/codegen/component-helpers.d.ts +6 -1
  60. package/dist/codegen/component-helpers.js +6 -1
  61. package/dist/codegen/component-props.d.ts +11 -2
  62. package/dist/codegen/component-props.js +50 -5
  63. package/dist/codegen/components.d.ts +6 -5
  64. package/dist/codegen/components.js +44 -21
  65. package/dist/codegen/control-flow.d.ts +2 -0
  66. package/dist/codegen/control-flow.js +23 -9
  67. package/dist/codegen/creation.js +5 -2
  68. package/dist/codegen/early-returns.d.ts +4 -3
  69. package/dist/codegen/early-returns.js +17 -5
  70. package/dist/codegen/emitter.d.ts +3 -0
  71. package/dist/codegen/emitter.js +18 -4
  72. package/dist/codegen/expressions.js +0 -2
  73. package/dist/codegen/functions.d.ts +11 -1
  74. package/dist/codegen/functions.js +31 -3
  75. package/dist/codegen/live.d.ts +60 -0
  76. package/dist/codegen/live.js +29 -0
  77. package/dist/codegen/mutations.d.ts +7 -0
  78. package/dist/codegen/mutations.js +43 -2
  79. package/dist/codegen/options.d.ts +2 -0
  80. package/dist/codegen/reactive.d.ts +4 -59
  81. package/dist/codegen/reactive.js +34 -72
  82. package/dist/codegen/runtime.js +6 -1
  83. package/dist/codegen/shared.d.ts +22 -0
  84. package/dist/codegen/shared.js +93 -0
  85. package/dist/codegen/simple-statements.d.ts +1 -1
  86. package/dist/codegen/statements.d.ts +5 -0
  87. package/dist/codegen/statements.js +35 -0
  88. package/dist/codegen/updates.d.ts +11 -3
  89. package/dist/codegen/updates.js +52 -4
  90. package/dist/codegen/web-components.d.ts +2 -2
  91. package/dist/codegen/web-components.js +2 -2
  92. package/dist/compile.js +6 -2
  93. package/dist/decorator-copy.d.ts +1 -1
  94. package/dist/decorator-copy.js +1 -0
  95. package/dist/decorator-names.d.ts +14 -0
  96. package/dist/decorator-names.js +56 -0
  97. package/dist/decorators.d.ts +2 -3
  98. package/dist/decorators.js +36 -14
  99. package/dist/inline-packages.d.ts +2 -1
  100. package/dist/inline-packages.js +9 -4
  101. package/dist/names.d.ts +4 -0
  102. package/dist/names.js +29 -0
  103. package/dist/parser/base.d.ts +1 -0
  104. package/dist/parser/base.js +1 -1
  105. package/dist/parser/control-flow.d.ts +5 -0
  106. package/dist/parser/control-flow.js +28 -2
  107. package/dist/parser/declarations.d.ts +5 -15
  108. package/dist/parser/declarations.js +15 -119
  109. package/dist/parser/decorators.d.ts +6 -2
  110. package/dist/parser/decorators.js +43 -19
  111. package/dist/parser/params.d.ts +21 -0
  112. package/dist/parser/params.js +143 -0
  113. package/dist/parser/simple-statements.d.ts +1 -0
  114. package/dist/parser/simple-statements.js +22 -6
  115. package/dist/parser/statements.d.ts +5 -2
  116. package/dist/parser/statements.js +18 -11
  117. package/dist/runtime/sources.js +3 -2
  118. package/dist/walk.js +3 -3
  119. package/package.json +2 -2
  120. package/src/runtime/decode.js +41 -10
  121. package/src/runtime/index.js +1 -0
  122. package/src/runtime/markup.js +27 -17
  123. package/src/runtime/shared.js +17 -0
@@ -0,0 +1,14 @@
1
+ import type * as ast from './ast.ts';
2
+ import type { DecoratorSource } from './decorators.ts';
3
+ /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
+ export declare function decoratedName(decorator: string, member: string): string;
5
+ /**
6
+ * The public members that a decorator gives the components it is applied to: its own and those of
7
+ * the decorators it includes, each with whether it changes (state and derived values do).
8
+ */
9
+ export declare function publicMembers(decorator: ast.DecoratorDeclaration, decorators: ReadonlyMap<string, DecoratorSource>, seen?: ReadonlySet<ast.DecoratorDeclaration>): Map<string, boolean>;
10
+ /**
11
+ * `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
12
+ * that is that of `cart`, `derived` when it changes, so that what reads it is updated.
13
+ */
14
+ export declare function aliases(included: DecoratorSource, use: ast.DecoratorUse, prefix: string, decorators: ReadonlyMap<string, DecoratorSource>, prefixes: ReadonlyMap<string, string> | undefined): ast.VariableDeclaration[];
@@ -0,0 +1,56 @@
1
+ // The names of the members of decorators in the components they are applied to, and the members
2
+ // that a decorator gives with those of the decorators it includes.
3
+ /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
+ export function decoratedName(decorator, member) {
5
+ return `$$${decorator}$${member}`;
6
+ }
7
+ /**
8
+ * The public members that a decorator gives the components it is applied to: its own and those of
9
+ * the decorators it includes, each with whether it changes (state and derived values do).
10
+ */
11
+ export function publicMembers(decorator, decorators, seen = new Set()) {
12
+ const members = new Map();
13
+ const inner = new Set([...seen, decorator]);
14
+ for (const use of decorator.includes) {
15
+ const included = decorators.get(use.name.name);
16
+ if (!included || inner.has(included.node))
17
+ continue;
18
+ for (const [name, changes] of publicMembers(included.node, decorators, inner)) {
19
+ members.set(name, changes);
20
+ }
21
+ }
22
+ for (const statement of decorator.body.body) {
23
+ if (statement.kind === 'FuncDeclaration' && statement.isPublic) {
24
+ members.set(statement.name.name, false);
25
+ }
26
+ else if (statement.kind === 'VariableDeclaration' && statement.isPublic) {
27
+ const changes = statement.keyword === 'state' || statement.keyword === 'derived';
28
+ for (const name of statement.names)
29
+ members.set(name.name, changes);
30
+ }
31
+ }
32
+ return members;
33
+ }
34
+ /**
35
+ * `@shop.count` of a member that `shop` gets from the `cart` it includes: a variable of `shop`
36
+ * that is that of `cart`, `derived` when it changes, so that what reads it is updated.
37
+ */
38
+ export function aliases(included, use, prefix, decorators, prefixes) {
39
+ const from = prefixes?.get(included.node.name.name) ?? included.node.name.name;
40
+ return [...publicMembers(included.node, decorators)].map(([member, changes]) => ({
41
+ kind: 'VariableDeclaration',
42
+ exported: false,
43
+ isPublic: false,
44
+ keyword: changes ? 'derived' : 'const',
45
+ names: [
46
+ { kind: 'Identifier', name: decoratedName(prefix, member), start: use.start, end: use.end },
47
+ ],
48
+ type: null,
49
+ values: [
50
+ { kind: 'Identifier', name: decoratedName(from, member), start: use.start, end: use.end },
51
+ ],
52
+ body: null,
53
+ start: use.start,
54
+ end: use.end,
55
+ }));
56
+ }
@@ -1,7 +1,6 @@
1
1
  import type * as ast from './ast.ts';
2
2
  import { type CopyListener } from './decorator-copy.ts';
3
- /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
4
- export declare function decoratedName(decorator: string, member: string): string;
3
+ export { decoratedName, publicMembers } from './decorator-names.ts';
5
4
  /**
6
5
  * Whether a declaration of an applied component gets an argument of a decorator. When the
7
6
  * argument reads the component's properties, the declaration is a `state` that the properties
@@ -45,7 +44,7 @@ export declare function moduleNamesOf(decorator: ast.DecoratorDeclaration, progr
45
44
  * The component with its decorators applied. Its body first declares the arguments of each
46
45
  * decorator and runs its body, from the top decorator down, then runs the component's own body.
47
46
  * The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
48
- * nor with the component's names, and `get(@visible.shown)` reads that variable. The `mount()` of
47
+ * nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
49
48
  * the decorators runs after the component's own, from the inside out, so it goes before every
50
49
  * `return` of the component's body.
51
50
  *
@@ -1,14 +1,12 @@
1
1
  import { renamer } from "./decorator-copy.js";
2
+ import { aliases, decoratedName } from "./decorator-names.js";
2
3
  import { declaredIn, freeNames, mentionsName, topLevelNames } from "./names.js";
3
4
  import { containsReturn } from "./recursion.js";
4
5
  import { forEachChild } from "./walk.js";
5
6
  // Applying decorators: a component with decorators becomes one component whose body runs the
6
7
  // bodies of its decorators first. The checker uses it to see what the inlined code uses, the
7
8
  // generator to write it.
8
- /** The JS name of a member or a parameter of a decorator, in the components it is applied to. */
9
- export function decoratedName(decorator, member) {
10
- return `$$${decorator}$${member}`;
11
- }
9
+ export { decoratedName, publicMembers } from "./decorator-names.js";
12
10
  const argumentDeclarations = new WeakSet();
13
11
  const wrappedContents = new WeakSet();
14
12
  /**
@@ -69,7 +67,7 @@ export function moduleNamesOf(decorator, program) {
69
67
  * The component with its decorators applied. Its body first declares the arguments of each
70
68
  * decorator and runs its body, from the top decorator down, then runs the component's own body.
71
69
  * The names of a decorator get its prefix, `$$visible$shown`, so they clash neither with each other
72
- * nor with the component's names, and `get(@visible.shown)` reads that variable. The `mount()` of
70
+ * nor with the component's names, and `@visible.shown` reads that variable. The `mount` of
73
71
  * the decorators runs after the component's own, from the inside out, so it goes before every
74
72
  * `return` of the component's body.
75
73
  *
@@ -83,37 +81,55 @@ export function moduleNamesOf(decorator, program) {
83
81
  export function decorate(component, decorators, copied, prefixes) {
84
82
  if (component.decorators.length === 0)
85
83
  return component;
86
- const props = new Set(component.params.map((param) => param.name.name));
84
+ // Arguments that read the properties, or such arguments of a decorator that includes another,
85
+ // are state: they change with them.
86
+ const reactive = new Set(component.params.map((param) => param.name.name));
87
87
  const setup = [];
88
88
  const mounts = [];
89
89
  const wrappers = [];
90
- for (const use of component.decorators) {
91
- const source = decorators.get(use.name.name);
92
- if (!source)
93
- continue;
90
+ // `argument` copies an argument into the component: one of the component stays as it is, one of
91
+ // a decorator it includes is that decorator's code.
92
+ const apply = (source, args, argument, applying) => {
94
93
  const decorator = source.node;
94
+ // A decorator that includes itself, through others too, is reported by the checker.
95
+ const inner = new Set([...applying, decorator]);
95
96
  const prefix = prefixes?.get(decorator.name.name) ?? decorator.name.name;
96
97
  const names = [...decoratorNames(decorator), ...source.moduleNames];
97
98
  const renames = membersOf(prefix, names);
98
99
  const copy = renamer(renames, copied, { decorators: prefixes, tags: source.tags });
99
100
  decorator.params.forEach((param, i) => {
100
- // A default value is the decorator's code, an argument the component's.
101
- const value = use.arguments[i] ?? (param.defaultValue && copy(param.defaultValue, new Set()));
101
+ const given = args[i];
102
+ // A default value is the decorator's code.
103
+ const value = given
104
+ ? argument(given)
105
+ : param.defaultValue && copy(param.defaultValue, new Set());
106
+ const name = renames.get(param.name.name);
107
+ const changes = value !== null && value !== undefined && mentionsName(value, reactive);
102
108
  const declaration = {
103
109
  kind: 'VariableDeclaration',
104
110
  exported: false,
105
111
  isPublic: false,
106
- keyword: value && mentionsName(value, props) ? 'state' : 'const',
107
- names: [{ ...param.name, name: renames.get(param.name.name) }],
112
+ keyword: changes ? 'state' : 'const',
113
+ names: [{ ...param.name, name }],
108
114
  type: param.type,
109
115
  values: value ? [value] : [],
116
+ body: null,
110
117
  start: param.start,
111
118
  end: param.end,
112
119
  };
120
+ if (changes)
121
+ reactive.add(name);
113
122
  argumentDeclarations.add(declaration);
114
123
  copied?.(declaration, param);
115
124
  setup.push(declaration);
116
125
  });
126
+ for (const use of decorator.includes) {
127
+ const included = decorators.get(use.name.name);
128
+ if (!included || inner.has(included.node))
129
+ continue;
130
+ apply(included, use.arguments, (value) => copy(value, new Set()), inner);
131
+ setup.push(...aliases(included, use, prefix, decorators, prefixes));
132
+ }
117
133
  const wrapper = wrapperOf(decorator);
118
134
  const own = [];
119
135
  for (const statement of decorator.body.body) {
@@ -128,6 +144,11 @@ export function decorate(component, decorators, copied, prefixes) {
128
144
  mounts.push(own);
129
145
  if (wrapper)
130
146
  wrappers.push(applyWrapper(source, wrapper, copied, prefix, prefixes));
147
+ };
148
+ for (const use of component.decorators) {
149
+ const source = decorators.get(use.name.name);
150
+ if (source)
151
+ apply(source, use.arguments, (value) => value, new Set());
131
152
  }
132
153
  const inside = mounts.reverse().flat();
133
154
  const chain = wrappers.reverse();
@@ -186,6 +207,7 @@ function wrapped(node, chain) {
186
207
  names: [{ ...wrapper.content }],
187
208
  type: wrapper.type,
188
209
  values: [value],
210
+ body: null,
189
211
  start: node.start,
190
212
  end: node.end,
191
213
  };
@@ -27,6 +27,7 @@ export interface ModulePackages {
27
27
  * recursive or a web component cannot be inlined, and neither can one that reaches components or
28
28
  * decorators the module imports: those would bring the names of a third module. Nor can code that
29
29
  * uses a component of the module as a value, which is the function in the module, or that assigns
30
- * a variable of the module: another module could only read it.
30
+ * a variable of the module: another module could only read it. Nor can code that reads state of
31
+ * the module, `state items` at its top level: it listens to the changes there.
31
32
  */
32
33
  export declare function inlinePackages(program: ast.Program): ModulePackages;
@@ -7,7 +7,8 @@ import { forEachChild } from "./walk.js";
7
7
  * recursive or a web component cannot be inlined, and neither can one that reaches components or
8
8
  * decorators the module imports: those would bring the names of a third module. Nor can code that
9
9
  * uses a component of the module as a value, which is the function in the module, or that assigns
10
- * a variable of the module: another module could only read it.
10
+ * a variable of the module: another module could only read it. Nor can code that reads state of
11
+ * the module, `state items` at its top level: it listens to the changes there.
11
12
  */
12
13
  export function inlinePackages(program) {
13
14
  const components = new Map();
@@ -30,9 +31,13 @@ export function inlinePackages(program) {
30
31
  };
31
32
  const topLevel = topLevelNames(program);
32
33
  const usesComponentValues = (node) => componentValues(node, (name) => components.has(name)).length > 0;
33
- const assignsModuleNames = (node) => {
34
+ const states = new Set(program.body.flatMap((statement) => statement.kind === 'VariableDeclaration' && statement.keyword === 'state'
35
+ ? statement.names.map((name) => name.name)
36
+ : []));
37
+ // It assigns a variable of the module or reads its state, which only the module can do.
38
+ const needsItsModule = (node) => {
34
39
  const assigned = assignedNames(node);
35
- return [...freeNames(node)].some((name) => topLevel.has(name) && assigned.has(name));
40
+ return [...freeNames(node)].some((name) => topLevel.has(name) && (assigned.has(name) || states.has(name)));
36
41
  };
37
42
  /** The package of a component, or of the tags in the code of a decorator. */
38
43
  const build = (root, tags) => {
@@ -69,7 +74,7 @@ export function inlinePackages(program) {
69
74
  found.components.set(current, component);
70
75
  if (usesComponentValues(expanded.get(current)))
71
76
  return false;
72
- if (assignsModuleNames(expanded.get(current)))
77
+ if (needsItsModule(expanded.get(current)))
73
78
  return false;
74
79
  for (const use of component.decorators) {
75
80
  const source = sources.get(use.name.name);
package/dist/names.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type * as ast from './ast.ts';
2
2
  /** Names assigned anywhere inside `node`: `x = ...`, `x += ...`, `x++`. */
3
3
  export declare function assignedNames(node: ast.Node, names?: Set<string>): Set<string>;
4
+ /** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
5
+ export declare function pathOf(node: ast.Expression): string[] | null;
6
+ /** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
7
+ export declare function assignedPaths(node: ast.Node, paths?: Set<string>): Set<string>;
4
8
  /** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
5
9
  export declare function mentionsName(node: ast.Node, names: ReadonlySet<string>): boolean;
6
10
  /** Every name declared inside `node`: parameters and all local declarations, nested ones too. */
package/dist/names.js CHANGED
@@ -19,6 +19,30 @@ export function assignedNames(node, names = new Set()) {
19
19
  forEachChild(node, (child) => assignedNames(child, names));
20
20
  return names;
21
21
  }
22
+ /** `order.delivery.kind`: the variable and the fields of a path, or `null` for other expressions. */
23
+ export function pathOf(node) {
24
+ if (node.kind === 'Identifier')
25
+ return [node.name];
26
+ if (node.kind !== 'MemberExpression' || node.optional)
27
+ return null;
28
+ const object = pathOf(node.object);
29
+ return object && [...object, node.property.name];
30
+ }
31
+ /** Paths of fields assigned anywhere inside `node`, e.g. `order.delivery` of `order.delivery = d`. */
32
+ export function assignedPaths(node, paths = new Set()) {
33
+ const targets = node.kind === 'AssignmentStatement'
34
+ ? node.targets
35
+ : node.kind === 'IncDecStatement'
36
+ ? [node.target]
37
+ : [];
38
+ for (const target of targets) {
39
+ const path = pathOf(target);
40
+ if (path && path.length > 1)
41
+ paths.add(path.join('.'));
42
+ }
43
+ forEachChild(node, (child) => assignedPaths(child, paths));
44
+ return paths;
45
+ }
22
46
  /** Whether an identifier with one of `names` occurs in `node`, whatever it refers to. */
23
47
  export function mentionsName(node, names) {
24
48
  if (node.kind === 'Identifier')
@@ -99,6 +123,7 @@ export function freeNames(component) {
99
123
  for (const name of node.names)
100
124
  declared.add(name.name);
101
125
  node.values.forEach(visit);
126
+ visit(node.body);
102
127
  return;
103
128
  case 'SelectCase':
104
129
  for (const name of node.names)
@@ -120,6 +145,8 @@ export function freeNames(component) {
120
145
  if (node.key)
121
146
  declared.add(node.key.name);
122
147
  visit(node.iterable);
148
+ if (node.withKey)
149
+ visit(node.withKey);
123
150
  visit(node.body);
124
151
  return;
125
152
  case 'CatchClause':
@@ -195,6 +222,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
195
222
  return;
196
223
  case 'VariableDeclaration':
197
224
  node.values.forEach(visit);
225
+ visit(node.body);
198
226
  return;
199
227
  case 'FuncDeclaration':
200
228
  case 'MethodDeclaration':
@@ -237,6 +265,7 @@ export function collectValueNames(node, names, isComponent = () => true) {
237
265
  return;
238
266
  case 'ForInStatement':
239
267
  visit(node.iterable);
268
+ visit(node.withKey ?? null);
240
269
  visit(node.body);
241
270
  return;
242
271
  case 'CatchClause':
@@ -25,6 +25,7 @@ export declare abstract class ParserBase {
25
25
  protected abstract parseClass(start: number, exported: boolean): ast.ClassDeclaration;
26
26
  protected abstract parseInterface(start: number, exported: boolean): ast.InterfaceDeclaration;
27
27
  protected abstract parseTypeAlias(start: number, exported: boolean): ast.TypeAliasDeclaration;
28
+ protected abstract parseFunctionBody(async?: boolean): ast.BlockStatement;
28
29
  protected abstract canStartType(offset?: number): boolean;
29
30
  protected abstract parseType(): ast.TypeNode;
30
31
  protected abstract parseTypeArgs(): ast.TypeNode[];
@@ -1,6 +1,6 @@
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 → declarations → decorators → classes → types → expressions → literals → markup → Parser
3
+ // flow → jumps → params → declarations → decorators → classes → types → expressions → literals → markup → Parser
4
4
  // (parser.ts). Statements, expressions and markup contain each other, so methods of later layers
5
5
  // that earlier ones call are declared here as abstract.
6
6
  //
@@ -17,6 +17,11 @@ export declare abstract class ControlFlowParser extends StatementParser {
17
17
  ast.SimpleStatement | null
18
18
  ];
19
19
  protected parseForIn(start: number, go?: boolean): ast.ForInStatement;
20
+ /**
21
+ * `with key todo.id` after the items of a `{for}` in markup: its blocks are found again by the
22
+ * key instead of the item. `with` and `key` are words only here, not keywords.
23
+ */
24
+ private parseWithKey;
20
25
  /** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
21
26
  protected parseGoFor(): ast.ForStatement | ast.ForInStatement;
22
27
  protected parseLoopBody(): ast.BlockStatement;
@@ -69,13 +69,39 @@ export class ControlFlowParser extends StatementParser {
69
69
  }
70
70
  this.expect('in');
71
71
  const iterable = this.parseHeaderExpression();
72
+ const withKey = this.parseWithKey(go);
73
+ const keyed = withKey ? { withKey } : {};
72
74
  if (!go) {
73
75
  const body = this.parseLoopBody();
74
- return { kind: 'ForInStatement', key, value, iterable, body, ...this.span(start) };
76
+ return { kind: 'ForInStatement', key, value, iterable, ...keyed, body, ...this.span(start) };
75
77
  }
76
78
  // The parts run at once in workers: nothing can leave the loop but `continue`.
77
79
  const body = this.withContext({ inLoop: true, inBreakable: false, inGoFor: true, labels: [] }, () => this.parseBlock());
78
- return { kind: 'ForInStatement', key, value, iterable, body, go, ...this.span(start) };
80
+ return {
81
+ kind: 'ForInStatement',
82
+ key,
83
+ value,
84
+ iterable,
85
+ ...keyed,
86
+ body,
87
+ go,
88
+ ...this.span(start),
89
+ };
90
+ }
91
+ /**
92
+ * `with key todo.id` after the items of a `{for}` in markup: its blocks are found again by the
93
+ * key instead of the item. `with` and `key` are words only here, not keywords.
94
+ */
95
+ parseWithKey(go) {
96
+ if (!this.checkWord('with'))
97
+ return null;
98
+ const start = this.next().start;
99
+ this.expectWord('key');
100
+ const key = this.parseHeaderExpression();
101
+ if (go || !this.context.inMarkup) {
102
+ this.error('"with key" finds again the blocks of a {for} in markup, and this loop has none: remove it', start, key.end);
103
+ }
104
+ return key;
79
105
  }
80
106
  /** `go for i, v in items { }`: the parts of the array run as goroutines, and the loop waits. */
81
107
  parseGoFor() {
@@ -1,29 +1,19 @@
1
1
  import type * as ast from '../ast.ts';
2
- import { JumpParser } from './jumps.ts';
3
- export declare abstract class DeclarationParser extends JumpParser {
2
+ import { ParamParser } from './params.ts';
3
+ export declare abstract class DeclarationParser extends ParamParser {
4
4
  protected parseImport(): ast.ImportDeclaration;
5
5
  protected parseImportSpecifier(): ast.ImportSpecifier;
6
+ /** `state items []Item = []` at the top level of a module: state its components share. */
7
+ protected parseDeclaration(start: number, exported: boolean): ast.Statement | undefined;
6
8
  protected parseExport(): ast.Statement;
7
9
  protected parseFuncDeclaration(start: number, exported: boolean): ast.FuncDeclaration;
8
10
  /**
9
11
  * `<T any, U Shape>` after a name; `<K, V any>` gives both the constraint, as in Go.
10
12
  */
11
13
  protected parseTypeParams(): ast.TypeParameter[];
12
- /**
13
- * `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
14
- * Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
15
- * properties: `(private name string)`.
16
- */
17
- protected parseParams(typesOptional: boolean, allowDefaults?: boolean, allowRest?: boolean, allowProperties?: boolean): ast.Parameter[];
18
- /** `private`, `readonly` or both before a parameter that is a property of its class. */
19
- private parsePropertyModifiers;
20
- /** `...attrs`: the attributes that a component passes on to one of its elements. */
21
- private parseRestParam;
22
- /** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
23
- protected parseResults(): ast.TypeNode[];
24
14
  /** `comp Name(properties) { ...; return <markup> }`, maybe after decorators. */
25
15
  protected parseComponent(start: number, exported: boolean, htmlTag?: ast.HtmlTag | null, decorators?: ast.DecoratorUse[]): ast.ComponentDeclaration;
26
- /** Like a function body, but its own statements may also declare `state`, `ref` and `mount()`. */
16
+ /** Like a function body, but its own statements may also declare `state`, `ref` and `mount`. */
27
17
  protected parseComponentBody(): ast.BlockStatement;
28
18
  /** A statement at the top level of a component or a decorator. */
29
19
  protected parseComponentStatement(): ast.Statement;
@@ -1,7 +1,7 @@
1
- import { JumpParser } from "./jumps.js";
2
- import { describe, FUNCTION_BODY, VISIBILITIES } from "./syntax.js";
3
- // Imports and exports, functions and components with their parameters and results.
4
- export class DeclarationParser extends JumpParser {
1
+ import { ParamParser } from "./params.js";
2
+ import { describe, FUNCTION_BODY } from "./syntax.js";
3
+ // Imports and exports, functions and components.
4
+ export class DeclarationParser extends ParamParser {
5
5
  parseImport() {
6
6
  const start = this.next().start;
7
7
  if (!this.context.topLevel) {
@@ -50,6 +50,16 @@ export class DeclarationParser extends JumpParser {
50
50
  }
51
51
  return { kind: 'ImportSpecifier', imported, local, ...this.span(token.start) };
52
52
  }
53
+ /** `state items []Item = []` at the top level of a module: state its components share. */
54
+ parseDeclaration(start, exported) {
55
+ if (!this.context.topLevel || !this.checkWord('state') || this.peek(1).kind !== 'Identifier') {
56
+ return super.parseDeclaration(start, exported);
57
+ }
58
+ this.requireMarkup('state of a module is');
59
+ const declaration = this.parseVariableDeclaration(start, exported);
60
+ this.endStatement();
61
+ return declaration;
62
+ }
53
63
  parseExport() {
54
64
  const start = this.next().start;
55
65
  if (!this.context.topLevel) {
@@ -109,120 +119,6 @@ export class DeclarationParser extends JumpParser {
109
119
  this.expectTypeClose('">" after the type parameters');
110
120
  return params;
111
121
  }
112
- /**
113
- * `(a, b number, c string)`. Names without a type take the type of the next typed name, as in
114
- * Go; only arrow functions may leave types out entirely. The parameters of a constructor can be
115
- * properties: `(private name string)`.
116
- */
117
- parseParams(typesOptional, allowDefaults = false, allowRest = false, allowProperties = false) {
118
- this.expect('(');
119
- const params = [];
120
- let untyped = 0;
121
- while (!this.check(')')) {
122
- if (this.check('...')) {
123
- params.push(this.parseRestParam(allowRest));
124
- break;
125
- }
126
- const paramStart = this.peek().start;
127
- const property = this.parsePropertyModifiers(allowProperties);
128
- const name = this.parseIdentifier('parameter name');
129
- const type = this.canStartType() ? this.parseType() : null;
130
- const defaultValue = allowDefaults && this.accept('=') ? this.parseExpression() : null;
131
- params.push({
132
- kind: 'Parameter',
133
- name,
134
- type,
135
- defaultValue,
136
- ...(property ? { property } : {}),
137
- ...this.span(paramStart),
138
- });
139
- if (type === null) {
140
- untyped++;
141
- }
142
- else {
143
- for (const param of params.slice(params.length - 1 - untyped))
144
- param.type = type;
145
- untyped = 0;
146
- }
147
- if (!this.accept(','))
148
- break;
149
- }
150
- this.expect(')');
151
- if (untyped > 0 && !typesOptional) {
152
- const param = params[params.length - untyped];
153
- this.error(`missing type for parameter "${param.name.name}"`, param.start, param.end);
154
- }
155
- return params;
156
- }
157
- /** `private`, `readonly` or both before a parameter that is a property of its class. */
158
- parsePropertyModifiers(allowed) {
159
- let visibility = null;
160
- let readonly = false;
161
- const start = this.peek().start;
162
- // Elsewhere `readonly number` is a parameter named readonly, and `private x number` an error.
163
- while (this.check('Identifier') &&
164
- (VISIBILITIES.has(this.peek().text) || this.peek().text === 'readonly') &&
165
- this.peek(1).kind === 'Identifier' &&
166
- (allowed || this.peek(2).kind === 'Identifier')) {
167
- const modifier = this.next();
168
- if (modifier.text === 'readonly') {
169
- if (readonly)
170
- this.error('duplicate "readonly" modifier', modifier.start, modifier.end);
171
- readonly = true;
172
- }
173
- else {
174
- if (visibility)
175
- this.error('duplicate visibility modifier', modifier.start, modifier.end);
176
- visibility = modifier.text;
177
- }
178
- }
179
- if (visibility === null && !readonly)
180
- return null;
181
- if (!allowed) {
182
- this.error('only the parameters of a constructor can be properties, e.g. constructor(private name string)', start, this.span(start).end);
183
- }
184
- return { visibility: visibility ?? 'public', ...(readonly ? { readonly: true } : {}) };
185
- }
186
- /** `...attrs`: the attributes that a component passes on to one of its elements. */
187
- parseRestParam(allowed) {
188
- const start = this.next().start;
189
- const name = this.parseIdentifier('parameter name');
190
- if (!allowed) {
191
- this.error(`...${name.name} is only for components: it takes the attributes of an element`, start, name.end);
192
- }
193
- if (this.canStartType() || this.check('=')) {
194
- const at = this.peek().start;
195
- // After the error, the type and the default are read, so they are not reported again.
196
- if (this.canStartType())
197
- this.parseType();
198
- if (this.accept('='))
199
- this.parseExpression();
200
- this.error(`...${name.name} has no type and no default: it gets the type of the element it is spread on`, at, this.span(at).end);
201
- }
202
- // After the error, the rest of the list is skipped up to the `)`.
203
- if (this.check(',')) {
204
- this.error(`...${name.name} is the last property of a component`, start, name.end);
205
- while (!this.check(')') && !this.check('EOF'))
206
- this.next();
207
- }
208
- return {
209
- kind: 'Parameter',
210
- name,
211
- type: null,
212
- defaultValue: null,
213
- rest: true,
214
- ...this.span(start),
215
- };
216
- }
217
- /** Result types after a parameter list: nothing, `T`, or `(T1, T2)`. */
218
- parseResults() {
219
- if (this.accept('('))
220
- return this.parseList(')', () => this.parseType());
221
- // `{` after the parameters starts the body, so an object result type needs parentheses.
222
- if (this.canStartType() && !this.check('{'))
223
- return [this.parseType()];
224
- return [];
225
- }
226
122
  /** `comp Name(properties) { ...; return <markup> }`, maybe after decorators. */
227
123
  parseComponent(start, exported, htmlTag = null, decorators = []) {
228
124
  this.requireMarkup('components are');
@@ -248,7 +144,7 @@ export class DeclarationParser extends JumpParser {
248
144
  ...this.span(start),
249
145
  };
250
146
  }
251
- /** Like a function body, but its own statements may also declare `state`, `ref` and `mount()`. */
147
+ /** Like a function body, but its own statements may also declare `state`, `ref` and `mount`. */
252
148
  parseComponentBody() {
253
149
  const start = this.expect('{').start;
254
150
  const body = [];
@@ -6,6 +6,10 @@ export declare abstract class DecoratorParser extends DeclarationParser {
6
6
  protected parseDecoratorDeclaration(start: number, exported: boolean): ast.DecoratorDeclaration;
7
7
  /** `needs cart, auth` after the parameters: `needs` is a keyword only here. */
8
8
  protected parseNeeds(): ast.Identifier[];
9
+ /** `includes visible(margin), cart` after `needs`: `includes` is a keyword only here. */
10
+ protected parseIncludes(): ast.DecoratorUse[];
11
+ /** `visible(margin)` or `cart`: like a decorator before `comp`, without the `@`. */
12
+ protected parseIncluded(): ast.DecoratorUse;
9
13
  /** Like the body of a component, but `state`, `const` and `func` may be `public`. */
10
14
  protected parseDecoratorBody(): ast.BlockStatement;
11
15
  /** `public` followed by a declaration; `public = 1` assigns a variable named `public`. */
@@ -24,7 +28,7 @@ export declare abstract class DecoratorParser extends DeclarationParser {
24
28
  /** `@visible.show`: a member of a decorator; a statement may start with it. */
25
29
  protected isDecoratorMember(): boolean;
26
30
  protected parseDecoratorMember(): ast.DecoratorMember;
27
- /** `get(@visible.shown)`: `get` is special only when its argument starts with `@`. */
31
+ /** `get(@visible.shown)`, the old way to read a member: reported, and read as the member. */
28
32
  protected isDecoratorGet(): boolean;
29
- protected parseDecoratorGet(): ast.DecoratorGet;
33
+ protected parseDecoratorGet(): ast.DecoratorMember;
30
34
  }