tova 0.7.0 → 0.9.4

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 (59) hide show
  1. package/bin/tova.js +1312 -139
  2. package/package.json +8 -1
  3. package/src/analyzer/analyzer.js +539 -11
  4. package/src/analyzer/browser-analyzer.js +56 -8
  5. package/src/analyzer/deploy-analyzer.js +44 -0
  6. package/src/analyzer/scope.js +7 -0
  7. package/src/analyzer/server-analyzer.js +33 -1
  8. package/src/codegen/base-codegen.js +1296 -23
  9. package/src/codegen/browser-codegen.js +725 -20
  10. package/src/codegen/codegen.js +87 -5
  11. package/src/codegen/deploy-codegen.js +49 -0
  12. package/src/codegen/server-codegen.js +54 -6
  13. package/src/codegen/shared-codegen.js +5 -0
  14. package/src/codegen/theme-codegen.js +69 -0
  15. package/src/codegen/wasm-codegen.js +6 -0
  16. package/src/config/edit-toml.js +6 -2
  17. package/src/config/git-resolver.js +128 -0
  18. package/src/config/lock-file.js +57 -0
  19. package/src/config/module-cache.js +58 -0
  20. package/src/config/module-entry.js +37 -0
  21. package/src/config/module-path.js +63 -0
  22. package/src/config/pkg-errors.js +62 -0
  23. package/src/config/resolve.js +26 -0
  24. package/src/config/resolver.js +139 -0
  25. package/src/config/search.js +28 -0
  26. package/src/config/semver.js +72 -0
  27. package/src/config/toml.js +61 -6
  28. package/src/deploy/deploy.js +217 -0
  29. package/src/deploy/infer.js +218 -0
  30. package/src/deploy/provision.js +315 -0
  31. package/src/diagnostics/security-scorecard.js +111 -0
  32. package/src/lexer/lexer.js +18 -3
  33. package/src/lsp/server.js +482 -0
  34. package/src/parser/animate-ast.js +45 -0
  35. package/src/parser/ast.js +39 -0
  36. package/src/parser/browser-ast.js +19 -1
  37. package/src/parser/browser-parser.js +221 -4
  38. package/src/parser/concurrency-ast.js +15 -0
  39. package/src/parser/concurrency-parser.js +236 -0
  40. package/src/parser/deploy-ast.js +37 -0
  41. package/src/parser/deploy-parser.js +132 -0
  42. package/src/parser/parser.js +42 -5
  43. package/src/parser/select-ast.js +39 -0
  44. package/src/parser/theme-ast.js +29 -0
  45. package/src/parser/theme-parser.js +70 -0
  46. package/src/registry/plugins/concurrency-plugin.js +32 -0
  47. package/src/registry/plugins/deploy-plugin.js +33 -0
  48. package/src/registry/plugins/theme-plugin.js +20 -0
  49. package/src/registry/register-all.js +6 -0
  50. package/src/runtime/charts.js +547 -0
  51. package/src/runtime/embedded.js +6 -2
  52. package/src/runtime/reactivity.js +60 -0
  53. package/src/runtime/router.js +703 -295
  54. package/src/runtime/table.js +606 -33
  55. package/src/stdlib/inline.js +365 -10
  56. package/src/stdlib/runtime-bridge.js +152 -0
  57. package/src/stdlib/string.js +84 -2
  58. package/src/stdlib/validation.js +1 -1
  59. package/src/version.js +1 -1
@@ -12,7 +12,23 @@ export function installBrowserAnalyzer(AnalyzerClass) {
12
12
 
13
13
  AnalyzerClass.prototype.visitBrowserBlock = function(node) {
14
14
  const prevScope = this.currentScope;
15
- this.currentScope = this.currentScope.child('browser');
15
+ let browserScope = null;
16
+ for (const ch of this.currentScope.children) {
17
+ if (ch.context === 'browser') { browserScope = ch; break; }
18
+ }
19
+ const isFirst = !browserScope;
20
+ this.currentScope = browserScope || this.currentScope.child('browser');
21
+
22
+ // On first browser block, pre-register state/computed/function names from ALL
23
+ // browser blocks so cross-file references resolve regardless of file order.
24
+ if (isFirst && this.ast && this.ast.body) {
25
+ for (const topNode of this.ast.body) {
26
+ if (topNode.type === 'BrowserBlock') {
27
+ this._preRegisterBrowserDecls(topNode.body);
28
+ }
29
+ }
30
+ }
31
+
16
32
  try {
17
33
  for (const stmt of node.body) {
18
34
  this.visitNode(stmt);
@@ -22,9 +38,26 @@ export function installBrowserAnalyzer(AnalyzerClass) {
22
38
  }
23
39
  };
24
40
 
41
+ AnalyzerClass.prototype._preRegisterBrowserDecls = function(stmts) {
42
+ for (const stmt of stmts) {
43
+ const name = stmt.name;
44
+ if (!name) continue;
45
+ let kind = null;
46
+ if (stmt.type === 'StateDeclaration') kind = 'state';
47
+ else if (stmt.type === 'ComputedDeclaration') kind = 'computed';
48
+ else if (stmt.type === 'FunctionDeclaration') kind = 'function';
49
+ else if (stmt.type === 'ComponentDeclaration') kind = 'component';
50
+ if (kind && !this.currentScope.symbols.has(name)) {
51
+ const sym = new Symbol(name, kind, stmt.typeAnnotation || null, kind === 'state', stmt.loc);
52
+ sym._forward = true;
53
+ try { this.currentScope.define(name, sym); } catch (e) { /* ignore */ }
54
+ }
55
+ }
56
+ };
57
+
25
58
  AnalyzerClass.prototype.visitStateDeclaration = function(node) {
26
59
  const ctx = this.currentScope.getContext();
27
- if (ctx !== 'browser') {
60
+ if (ctx !== 'browser' && !this._inPubComponent) {
28
61
  this.error(`'state' can only be used inside a browser block`, node.loc, "move this inside a browser { } block", { code: 'E302' });
29
62
  }
30
63
  try {
@@ -38,7 +71,7 @@ export function installBrowserAnalyzer(AnalyzerClass) {
38
71
 
39
72
  AnalyzerClass.prototype.visitComputedDeclaration = function(node) {
40
73
  const ctx = this.currentScope.getContext();
41
- if (ctx !== 'browser') {
74
+ if (ctx !== 'browser' && !this._inPubComponent) {
42
75
  this.error(`'computed' can only be used inside a browser block`, node.loc, "move this inside a browser { } block", { code: 'E302' });
43
76
  }
44
77
  try {
@@ -52,7 +85,7 @@ export function installBrowserAnalyzer(AnalyzerClass) {
52
85
 
53
86
  AnalyzerClass.prototype.visitEffectDeclaration = function(node) {
54
87
  const ctx = this.currentScope.getContext();
55
- if (ctx !== 'browser') {
88
+ if (ctx !== 'browser' && !this._inPubComponent) {
56
89
  this.error(`'effect' can only be used inside a browser block`, node.loc, "move this inside a browser { } block", { code: 'E302' });
57
90
  }
58
91
  this.visitNode(node.body);
@@ -60,19 +93,32 @@ export function installBrowserAnalyzer(AnalyzerClass) {
60
93
 
61
94
  AnalyzerClass.prototype.visitComponentDeclaration = function(node) {
62
95
  const ctx = this.currentScope.getContext();
63
- if (ctx !== 'browser') {
96
+ if (ctx !== 'browser' && !node.isPublic) {
64
97
  this.error(`'component' can only be used inside a browser block`, node.loc, "move this inside a browser { } block", { code: 'E302' });
65
98
  }
66
- this._checkNamingConvention(node.name, 'component', node.loc);
99
+ // Skip naming convention check for compound components (e.g. Dialog.Title)
100
+ // since "Dialog.Title" isn't a single PascalCase identifier
101
+ if (!node.parent) {
102
+ this._checkNamingConvention(node.name, 'component', node.loc);
103
+ }
104
+ // For compound components, register with the parent name (already defined)
105
+ // and use the full name for the symbol definition
106
+ const symbolName = node.name;
67
107
  try {
68
- this.currentScope.define(node.name,
69
- new Symbol(node.name, 'component', null, false, node.loc));
108
+ this.currentScope.define(symbolName,
109
+ new Symbol(symbolName, 'component', null, false, node.loc));
70
110
  } catch (e) {
71
111
  this.error(e.message);
72
112
  }
73
113
 
74
114
  const prevScope = this.currentScope;
75
115
  this.currentScope = this.currentScope.child('function');
116
+ // Store component prop names for variant() validation in style blocks
117
+ const prevComponentProps = this._currentComponentProps;
118
+ this._currentComponentProps = node.params.map(p => p.name);
119
+ // Track pub component context so state/computed/effect are allowed inside
120
+ const prevInPubComponent = this._inPubComponent;
121
+ if (node.isPublic) this._inPubComponent = true;
76
122
  for (const param of node.params) {
77
123
  try {
78
124
  this.currentScope.define(param.name,
@@ -86,6 +132,8 @@ export function installBrowserAnalyzer(AnalyzerClass) {
86
132
  this.visitNode(child);
87
133
  }
88
134
  } finally {
135
+ this._currentComponentProps = prevComponentProps;
136
+ this._inPubComponent = prevInPubComponent;
89
137
  this.currentScope = prevScope;
90
138
  }
91
139
  };
@@ -0,0 +1,44 @@
1
+ // Deploy-specific analyzer methods for the Tova language
2
+ // Extracted from analyzer.js for lazy loading — only loaded when deploy { } blocks are encountered.
3
+
4
+ const KNOWN_DEPLOY_FIELDS = new Set([
5
+ 'server', 'domain', 'instances', 'memory', 'branch',
6
+ 'health', 'health_interval', 'health_timeout',
7
+ 'restart_on_failure', 'keep_releases',
8
+ ]);
9
+
10
+ const REQUIRED_DEPLOY_FIELDS = ['server', 'domain'];
11
+
12
+ export function installDeployAnalyzer(AnalyzerClass) {
13
+ if (AnalyzerClass.prototype._deployAnalyzerInstalled) return;
14
+ AnalyzerClass.prototype._deployAnalyzerInstalled = true;
15
+
16
+ AnalyzerClass.prototype.visitDeployBlock = function(node) {
17
+ // Collect config field keys present in the deploy block body
18
+ const presentFields = new Set();
19
+ for (const stmt of node.body) {
20
+ if (stmt.type === 'DeployConfigField') {
21
+ // Validate unknown fields
22
+ if (!KNOWN_DEPLOY_FIELDS.has(stmt.key)) {
23
+ this.error(
24
+ `Unknown deploy config field "${stmt.key}"`,
25
+ stmt.loc,
26
+ `Known fields: ${[...KNOWN_DEPLOY_FIELDS].join(', ')}`
27
+ );
28
+ }
29
+ presentFields.add(stmt.key);
30
+ }
31
+ // DeployEnvBlock and DeployDbBlock are valid sub-blocks — no additional validation needed
32
+ }
33
+
34
+ // Validate required fields
35
+ for (const required of REQUIRED_DEPLOY_FIELDS) {
36
+ if (!presentFields.has(required)) {
37
+ this.error(
38
+ `Deploy block "${node.name}" is missing required field "${required}"`,
39
+ node.loc
40
+ );
41
+ }
42
+ }
43
+ };
44
+ }
@@ -28,6 +28,13 @@ export class Scope {
28
28
  const existing = this.symbols.get(name);
29
29
  // Allow user code to shadow builtins
30
30
  if (existing.kind === 'builtin') {
31
+ if (existing.used) symbol.used = true;
32
+ this.symbols.set(name, symbol);
33
+ return;
34
+ }
35
+ // Allow real definitions to overwrite forward-declared symbols
36
+ if (existing._forward) {
37
+ if (existing.used) symbol.used = true;
31
38
  this.symbols.set(name, symbol);
32
39
  return;
33
40
  }
@@ -37,7 +37,22 @@ export function installServerAnalyzer(AnalyzerClass) {
37
37
  const prevScope = this.currentScope;
38
38
  const prevServerBlockName = this._currentServerBlockName;
39
39
  this._currentServerBlockName = node.name || null;
40
- this.currentScope = this.currentScope.child('server');
40
+ let serverScope = null;
41
+ for (const ch of this.currentScope.children) {
42
+ if (ch.context === 'server') { serverScope = ch; break; }
43
+ }
44
+ const isFirst = !serverScope;
45
+ this.currentScope = serverScope || this.currentScope.child('server');
46
+
47
+ // On first server block, pre-register function/var/type names from ALL
48
+ // server blocks so cross-file references resolve regardless of file order.
49
+ if (isFirst && this.ast && this.ast.body) {
50
+ for (const topNode of this.ast.body) {
51
+ if (topNode.type === 'ServerBlock') {
52
+ this._preRegisterServerDecls(topNode.body);
53
+ }
54
+ }
55
+ }
41
56
 
42
57
  try {
43
58
  // Register peer server block names as valid identifiers in this scope
@@ -76,6 +91,23 @@ export function installServerAnalyzer(AnalyzerClass) {
76
91
  }
77
92
  };
78
93
 
94
+ AnalyzerClass.prototype._preRegisterServerDecls = function(stmts) {
95
+ for (const stmt of stmts) {
96
+ const name = stmt.name;
97
+ if (!name) continue;
98
+ let kind = null;
99
+ if (stmt.type === 'FunctionDeclaration') kind = 'function';
100
+ else if (stmt.type === 'VarDeclaration') kind = 'variable';
101
+ else if (stmt.type === 'TypeDeclaration') kind = 'type';
102
+ else if (stmt.type === 'ModelDeclaration') kind = 'type';
103
+ if (kind && !this.currentScope.symbols.has(name)) {
104
+ const sym = new Symbol(name, kind, stmt.typeAnnotation || null, kind === 'variable', stmt.loc);
105
+ sym._forward = true;
106
+ try { this.currentScope.define(name, sym); } catch (e) { /* ignore */ }
107
+ }
108
+ }
109
+ };
110
+
79
111
  AnalyzerClass.prototype.visitRouteDeclaration = function(node) {
80
112
  const ctx = this.currentScope.getContext();
81
113
  if (ctx !== 'server') {