fastscript 1.0.0 → 3.0.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 (119) hide show
  1. package/CHANGELOG.md +38 -7
  2. package/LICENSE +33 -21
  3. package/README.md +605 -73
  4. package/node_modules/@fastscript/core-private/BOUNDARY.json +15 -0
  5. package/node_modules/@fastscript/core-private/README.md +5 -0
  6. package/node_modules/@fastscript/core-private/package.json +34 -0
  7. package/node_modules/@fastscript/core-private/src/asset-optimizer.mjs +67 -0
  8. package/node_modules/@fastscript/core-private/src/audit-log.mjs +50 -0
  9. package/node_modules/@fastscript/core-private/src/auth-flows.mjs +29 -0
  10. package/node_modules/@fastscript/core-private/src/auth.mjs +115 -0
  11. package/node_modules/@fastscript/core-private/src/bench.mjs +45 -0
  12. package/node_modules/@fastscript/core-private/src/build.mjs +670 -0
  13. package/node_modules/@fastscript/core-private/src/cache.mjs +248 -0
  14. package/node_modules/@fastscript/core-private/src/check.mjs +22 -0
  15. package/node_modules/@fastscript/core-private/src/cli.mjs +95 -0
  16. package/node_modules/@fastscript/core-private/src/compat.mjs +128 -0
  17. package/node_modules/@fastscript/core-private/src/create.mjs +278 -0
  18. package/node_modules/@fastscript/core-private/src/csp.mjs +26 -0
  19. package/node_modules/@fastscript/core-private/src/db-cli.mjs +185 -0
  20. package/node_modules/@fastscript/core-private/src/db-postgres-collection.mjs +110 -0
  21. package/node_modules/@fastscript/core-private/src/db-postgres.mjs +40 -0
  22. package/node_modules/@fastscript/core-private/src/db.mjs +103 -0
  23. package/node_modules/@fastscript/core-private/src/deploy.mjs +662 -0
  24. package/node_modules/@fastscript/core-private/src/dev.mjs +5 -0
  25. package/node_modules/@fastscript/core-private/src/docs-search.mjs +35 -0
  26. package/node_modules/@fastscript/core-private/src/env.mjs +118 -0
  27. package/node_modules/@fastscript/core-private/src/export.mjs +83 -0
  28. package/node_modules/@fastscript/core-private/src/fs-diagnostics.mjs +70 -0
  29. package/node_modules/@fastscript/core-private/src/fs-error-codes.mjs +141 -0
  30. package/node_modules/@fastscript/core-private/src/fs-formatter.mjs +66 -0
  31. package/node_modules/@fastscript/core-private/src/fs-linter.mjs +274 -0
  32. package/node_modules/@fastscript/core-private/src/fs-normalize.mjs +121 -0
  33. package/node_modules/@fastscript/core-private/src/fs-parser.mjs +1120 -0
  34. package/node_modules/@fastscript/core-private/src/generated/docs-search-index.mjs +3182 -0
  35. package/node_modules/@fastscript/core-private/src/i18n.mjs +25 -0
  36. package/node_modules/@fastscript/core-private/src/interop.mjs +16 -0
  37. package/node_modules/@fastscript/core-private/src/jobs.mjs +378 -0
  38. package/node_modules/@fastscript/core-private/src/logger.mjs +27 -0
  39. package/node_modules/@fastscript/core-private/src/metrics.mjs +45 -0
  40. package/node_modules/@fastscript/core-private/src/middleware.mjs +14 -0
  41. package/node_modules/@fastscript/core-private/src/migrate.mjs +81 -0
  42. package/node_modules/@fastscript/core-private/src/migration-wizard.mjs +16 -0
  43. package/node_modules/@fastscript/core-private/src/module-loader.mjs +46 -0
  44. package/node_modules/@fastscript/core-private/src/oauth-providers.mjs +103 -0
  45. package/node_modules/@fastscript/core-private/src/observability.mjs +21 -0
  46. package/node_modules/@fastscript/core-private/src/plugins.mjs +194 -0
  47. package/node_modules/@fastscript/core-private/src/retention.mjs +57 -0
  48. package/node_modules/@fastscript/core-private/src/routes.mjs +178 -0
  49. package/node_modules/@fastscript/core-private/src/scheduler.mjs +104 -0
  50. package/node_modules/@fastscript/core-private/src/security.mjs +233 -0
  51. package/node_modules/@fastscript/core-private/src/server-runtime.mjs +849 -0
  52. package/node_modules/@fastscript/core-private/src/serverless-handler.mjs +20 -0
  53. package/node_modules/@fastscript/core-private/src/session-policy.mjs +38 -0
  54. package/node_modules/@fastscript/core-private/src/start.mjs +10 -0
  55. package/node_modules/@fastscript/core-private/src/storage.mjs +155 -0
  56. package/node_modules/@fastscript/core-private/src/style-primitives.mjs +538 -0
  57. package/node_modules/@fastscript/core-private/src/style-system.mjs +461 -0
  58. package/node_modules/@fastscript/core-private/src/tenant.mjs +55 -0
  59. package/node_modules/@fastscript/core-private/src/typecheck.mjs +1466 -0
  60. package/node_modules/@fastscript/core-private/src/validate.mjs +22 -0
  61. package/node_modules/@fastscript/core-private/src/validation.mjs +88 -0
  62. package/node_modules/@fastscript/core-private/src/webhook.mjs +81 -0
  63. package/node_modules/@fastscript/core-private/src/worker.mjs +24 -0
  64. package/package.json +108 -14
  65. package/src/asset-optimizer.mjs +67 -0
  66. package/src/audit-log.mjs +50 -0
  67. package/src/auth.mjs +1 -115
  68. package/src/bench.mjs +20 -7
  69. package/src/benchmark-discipline.mjs +39 -0
  70. package/src/build.mjs +1 -234
  71. package/src/cache.mjs +210 -20
  72. package/src/cli.mjs +65 -6
  73. package/src/compat.mjs +8 -10
  74. package/src/conversion-manifest.mjs +101 -0
  75. package/src/create.mjs +71 -17
  76. package/src/csp.mjs +26 -0
  77. package/src/db-cli.mjs +152 -8
  78. package/src/db-postgres-collection.mjs +110 -0
  79. package/src/deploy.mjs +1 -65
  80. package/src/diagnostics.mjs +100 -0
  81. package/src/docs-search.mjs +35 -0
  82. package/src/env.mjs +34 -5
  83. package/src/fs-diagnostics.mjs +70 -0
  84. package/src/fs-error-codes.mjs +126 -0
  85. package/src/fs-formatter.mjs +66 -0
  86. package/src/fs-linter.mjs +274 -0
  87. package/src/fs-normalize.mjs +52 -239
  88. package/src/fs-parser.mjs +1 -0
  89. package/src/generated/docs-search-index.mjs +3591 -0
  90. package/src/i18n.mjs +25 -0
  91. package/src/jobs.mjs +283 -32
  92. package/src/metrics.mjs +45 -0
  93. package/src/migrate-rollback.mjs +144 -0
  94. package/src/migrate.mjs +1275 -47
  95. package/src/migration-wizard.mjs +42 -0
  96. package/src/module-loader.mjs +22 -11
  97. package/src/oauth-providers.mjs +103 -0
  98. package/src/permissions-cli.mjs +112 -0
  99. package/src/plugins.mjs +194 -0
  100. package/src/profile.mjs +95 -0
  101. package/src/regression-guard.mjs +245 -0
  102. package/src/retention.mjs +57 -0
  103. package/src/routes.mjs +178 -0
  104. package/src/runtime-permissions.mjs +299 -0
  105. package/src/scheduler.mjs +104 -0
  106. package/src/security.mjs +197 -19
  107. package/src/server-runtime.mjs +1 -339
  108. package/src/serverless-handler.mjs +20 -0
  109. package/src/session-policy.mjs +38 -0
  110. package/src/storage.mjs +1 -56
  111. package/src/style-system.mjs +461 -0
  112. package/src/tenant.mjs +55 -0
  113. package/src/trace.mjs +95 -0
  114. package/src/typecheck.mjs +1 -0
  115. package/src/validate.mjs +13 -1
  116. package/src/validation.mjs +14 -5
  117. package/src/webhook.mjs +1 -71
  118. package/src/worker.mjs +23 -4
  119. package/src/language-spec.mjs +0 -58
@@ -0,0 +1,274 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { extname, join, resolve } from "node:path";
3
+ import { parseFastScript } from "./fs-parser.mjs";
4
+
5
+ function walk(dir) {
6
+ if (!existsSync(dir)) return [];
7
+ const out = [];
8
+ for (const entry of readdirSync(dir)) {
9
+ const full = join(dir, entry);
10
+ const st = statSync(full);
11
+ if (st.isDirectory()) out.push(...walk(full));
12
+ else if (st.isFile()) out.push(full);
13
+ }
14
+ return out;
15
+ }
16
+
17
+ function spanFromNode(node, sourceLength) {
18
+ const start = Math.max(0, Math.min(sourceLength, Number(node?.fsRange?.[0] ?? node?.start ?? 0)));
19
+ const end = Math.max(start, Math.min(sourceLength, Number(node?.fsRange?.[1] ?? node?.end ?? start)));
20
+ return { start, end };
21
+ }
22
+
23
+ function createIssue({ file, source, lineStarts, code, severity, message, span, fix = null }) {
24
+ const start = Math.max(0, Math.min(source.length, span?.start ?? 0));
25
+ const end = Math.max(start, Math.min(source.length, span?.end ?? start + 1));
26
+ const loc = lineFromOffset(lineStarts, start);
27
+ return {
28
+ file,
29
+ line: loc.line,
30
+ column: loc.column,
31
+ span: { start, end },
32
+ code,
33
+ severity,
34
+ message,
35
+ fix,
36
+ };
37
+ }
38
+
39
+ function createLineStarts(source) {
40
+ const out = [0];
41
+ for (let i = 0; i < source.length; i += 1) {
42
+ if (source[i] === "\n") out.push(i + 1);
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function lineFromOffset(lineStarts, offset) {
48
+ let lo = 0;
49
+ let hi = lineStarts.length - 1;
50
+ while (lo <= hi) {
51
+ const mid = (lo + hi) >> 1;
52
+ const start = lineStarts[mid];
53
+ const next = lineStarts[mid + 1] ?? Number.POSITIVE_INFINITY;
54
+ if (offset < start) hi = mid - 1;
55
+ else if (offset >= next) lo = mid + 1;
56
+ else return { line: mid + 1, column: offset - start + 1 };
57
+ }
58
+ return { line: 1, column: 1 };
59
+ }
60
+
61
+ function traverse(node, visitor, parent = null) {
62
+ if (!node || typeof node !== "object") return;
63
+ visitor(node, parent);
64
+ for (const value of Object.values(node)) {
65
+ if (!value) continue;
66
+ if (Array.isArray(value)) {
67
+ for (const item of value) {
68
+ if (item && typeof item === "object") traverse(item, visitor, node);
69
+ }
70
+ } else if (typeof value === "object" && typeof value.type === "string") {
71
+ traverse(value, visitor, node);
72
+ }
73
+ }
74
+ }
75
+
76
+ function collectReassignedNames(estree) {
77
+ const names = new Set();
78
+ traverse(estree, (node) => {
79
+ if (node.type === "AssignmentExpression" && node.left?.type === "Identifier") {
80
+ names.add(node.left.name);
81
+ }
82
+ if (node.type === "UpdateExpression" && node.argument?.type === "Identifier") {
83
+ names.add(node.argument.name);
84
+ }
85
+ });
86
+ return names;
87
+ }
88
+
89
+ function declarationKeywordFixSpan(source, declaration, keyword) {
90
+ const span = spanFromNode(declaration, source.length);
91
+ const window = source.slice(span.start, Math.min(span.end, span.start + 64));
92
+ const pattern = new RegExp(`\\b${keyword}\\b`);
93
+ const match = pattern.exec(window);
94
+ if (!match) return null;
95
+ return {
96
+ start: span.start + match.index,
97
+ end: span.start + match.index + keyword.length,
98
+ };
99
+ }
100
+
101
+ function applyFixes(source, fixes) {
102
+ if (!fixes.length) return source;
103
+ const sorted = [...fixes]
104
+ .filter((fix) => fix && Number.isFinite(fix.start) && Number.isFinite(fix.end) && fix.start <= fix.end)
105
+ .sort((a, b) => {
106
+ if (a.start !== b.start) return a.start - b.start;
107
+ return a.end - b.end;
108
+ });
109
+
110
+ const nonOverlapping = [];
111
+ let cursor = -1;
112
+ for (const fix of sorted) {
113
+ if (fix.start < cursor) continue;
114
+ nonOverlapping.push(fix);
115
+ cursor = fix.end;
116
+ }
117
+
118
+ let output = source;
119
+ for (let i = nonOverlapping.length - 1; i >= 0; i -= 1) {
120
+ const fix = nonOverlapping[i];
121
+ output = `${output.slice(0, fix.start)}${fix.text}${output.slice(fix.end)}`;
122
+ }
123
+ return output;
124
+ }
125
+
126
+ function lintSource(source, { file }) {
127
+ const text = String(source ?? "");
128
+ const lineStarts = createLineStarts(text);
129
+ const parsed = parseFastScript(text, { file, mode: "lenient", recover: true });
130
+ const issues = [];
131
+ const fixes = [];
132
+
133
+ for (const diagnostic of parsed.diagnostics) {
134
+ issues.push(
135
+ createIssue({
136
+ file,
137
+ source: text,
138
+ lineStarts,
139
+ code: diagnostic.code,
140
+ severity: diagnostic.severity || "error",
141
+ message: diagnostic.message,
142
+ span: diagnostic.span,
143
+ }),
144
+ );
145
+ }
146
+
147
+ const todoPattern = /\bTODO_ERROR\b/g;
148
+ for (const match of text.matchAll(todoPattern)) {
149
+ const start = match.index ?? 0;
150
+ issues.push(
151
+ createIssue({
152
+ file,
153
+ source: text,
154
+ lineStarts,
155
+ code: "FS3001",
156
+ severity: "error",
157
+ message: "Remove TODO_ERROR token.",
158
+ span: { start, end: start + match[0].length },
159
+ }),
160
+ );
161
+ }
162
+
163
+ if (!parsed.estree) return { issues, fixes };
164
+
165
+ const reassignedNames = collectReassignedNames(parsed.estree);
166
+ traverse(parsed.estree, (node) => {
167
+ if (node.type === "VariableDeclaration") {
168
+ if (node.kind === "var") {
169
+ const fixSpan = declarationKeywordFixSpan(text, node, "var");
170
+ const fix = fixSpan ? { ...fixSpan, text: "let" } : null;
171
+ issues.push(
172
+ createIssue({
173
+ file,
174
+ source: text,
175
+ lineStarts,
176
+ code: "FS3002",
177
+ severity: "warning",
178
+ message: "Prefer `let` or `const` over `var`.",
179
+ span: spanFromNode(node, text.length),
180
+ fix,
181
+ }),
182
+ );
183
+ if (fix) fixes.push(fix);
184
+ }
185
+
186
+ if (node.kind === "let") {
187
+ const names = [];
188
+ for (const declaration of node.declarations || []) {
189
+ if (declaration.id?.type === "Identifier") names.push(declaration.id.name);
190
+ }
191
+ if (names.length > 0 && names.every((name) => !reassignedNames.has(name))) {
192
+ const fixSpan = declarationKeywordFixSpan(text, node, "let");
193
+ const fix = fixSpan ? { ...fixSpan, text: "const" } : null;
194
+ issues.push(
195
+ createIssue({
196
+ file,
197
+ source: text,
198
+ lineStarts,
199
+ code: "FS3004",
200
+ severity: "warning",
201
+ message: "Binding can be `const`.",
202
+ span: spanFromNode(node, text.length),
203
+ fix,
204
+ }),
205
+ );
206
+ if (fix) fixes.push(fix);
207
+ }
208
+ }
209
+ }
210
+
211
+ if (node.type === "TemplateElement" && /<script[\s>]/i.test(node.value?.raw || "")) {
212
+ issues.push(
213
+ createIssue({
214
+ file,
215
+ source: text,
216
+ lineStarts,
217
+ code: "FS3003",
218
+ severity: "warning",
219
+ message: "Avoid inline <script> tags in template literals.",
220
+ span: spanFromNode(node, text.length),
221
+ }),
222
+ );
223
+ }
224
+ });
225
+
226
+ return { issues, fixes };
227
+ }
228
+
229
+ export async function runLint(args = []) {
230
+ let target = "app";
231
+ let mode = "fail";
232
+ let fix = false;
233
+ for (let i = 0; i < args.length; i += 1) {
234
+ if (args[i] === "--path") target = args[i + 1] || target;
235
+ if (args[i] === "--mode") mode = (args[i + 1] || mode).toLowerCase();
236
+ if (args[i] === "--fix") fix = true;
237
+ }
238
+
239
+ const base = resolve(target);
240
+ const files = walk(base).filter((file) => extname(file) === ".fs");
241
+ const issues = [];
242
+
243
+ for (const file of files) {
244
+ const source = readFileSync(file, "utf8");
245
+ const result = lintSource(source, { file });
246
+ let next = source;
247
+ if (fix && result.fixes.length > 0) {
248
+ next = applyFixes(source, result.fixes);
249
+ if (next !== source) writeFileSync(file, next, "utf8");
250
+ }
251
+ issues.push(...result.issues);
252
+ }
253
+
254
+ issues.sort((a, b) => {
255
+ if (a.file !== b.file) return a.file.localeCompare(b.file);
256
+ if (a.span.start !== b.span.start) return a.span.start - b.span.start;
257
+ if (a.code !== b.code) return a.code.localeCompare(b.code);
258
+ return a.message.localeCompare(b.message);
259
+ });
260
+
261
+ for (const issue of issues) {
262
+ console.log(`${issue.file}:${issue.line}:${issue.column} ${issue.code} ${issue.severity} ${issue.message}`);
263
+ }
264
+
265
+ const blocking = issues.filter((issue) => issue.severity === "error");
266
+ if (blocking.length > 0 && mode !== "pass") {
267
+ const error = new Error(`lint failed: ${blocking.length} blocking issue(s)`);
268
+ error.status = 1;
269
+ error.details = blocking;
270
+ throw error;
271
+ }
272
+
273
+ console.log(`lint complete: ${files.length} file(s), ${issues.length} issue(s), mode=${mode}, fix=${fix}`);
274
+ }
@@ -1,232 +1,21 @@
1
- const reactiveDeclPattern = /^(\s*)~([A-Za-z_$][\w$]*)(\s*=\s*.*)$/;
2
- const stateDeclPattern = /^(\s*)state\s+([A-Za-z_$][\w$]*)(\s*=\s*.*)$/;
3
- const fnDeclPattern = /^(\s*)(export\s+)?fn\s+([A-Za-z_$][\w$]*)\s*\(/;
4
- const reactiveStartPattern = /^\s*~/;
5
- const stateStartPattern = /^\s*state\b/;
6
- const emptyImportClausePattern = /^\s*import\s*\{\s*\}\s*from\s*["'][^"']+["']/;
7
- const invalidFnPattern = /^\s*(export\s+)?fn\b(?!\s+[A-Za-z_$][\w$]*\s*\()/;
8
- const relativeJsImportPattern = /from\s+["'](\.?\.?\/[^"']+)\.(js|jsx|ts|tsx)["']/;
9
-
10
- function nowMs() {
11
- if (typeof performance !== "undefined" && typeof performance.now === "function") {
12
- return performance.now();
13
- }
14
- return Date.now();
15
- }
16
-
17
- function toDiag(severity, line, col, code, message, fix = null) {
18
- return { severity, line, col, code, message, fix };
19
- }
20
-
21
- function normalizeFastScriptInternal(source, stats) {
22
- const lines = source.split(/\r?\n/);
23
- const out = [];
24
-
25
- for (const line of lines) {
26
- const m = line.match(reactiveDeclPattern);
27
- if (m) {
28
- stats.reactiveToLet += 1;
29
- out.push(`${m[1]}let ${m[2]}${m[3]}`);
30
- continue;
31
- }
32
- const s = line.match(stateDeclPattern);
33
- if (s) {
34
- stats.stateToLet += 1;
35
- out.push(`${s[1]}let ${s[2]}${s[3]}`);
36
- continue;
37
- }
38
- const f = line.match(fnDeclPattern);
39
- if (f) {
40
- stats.fnToFunction += 1;
41
- out.push(line.replace(fnDeclPattern, `${f[1]}${f[2] ?? ""}function ${f[3]}(`));
42
- continue;
43
- }
44
- out.push(line);
45
- }
46
-
47
- return out.join("\n");
48
- }
49
-
50
- export function analyzeFastScriptSource(source, { filename = "input.fs" } = {}) {
51
- const lines = source.split(/\r?\n/);
52
- const diagnostics = [];
53
- const declaredState = new Map();
54
- const isFs = /\.fs$/i.test(filename);
55
-
56
- for (let i = 0; i < lines.length; i += 1) {
57
- const lineNo = i + 1;
58
- const line = lines[i];
59
-
60
- if (!line.trim()) continue;
61
-
62
- const reactive = line.match(reactiveDeclPattern);
63
- if (reactive) {
64
- const name = reactive[2];
65
- const first = declaredState.get(name);
66
- if (first !== undefined) {
67
- diagnostics.push(
68
- toDiag(
69
- "warning",
70
- lineNo,
71
- Math.max(1, line.indexOf(name) + 1),
72
- "FS_DUP_STATE",
73
- `Duplicate reactive/state declaration "${name}" (first seen on line ${first})`,
74
- "Rename one declaration to avoid shadowing confusion.",
75
- ),
76
- );
77
- } else {
78
- declaredState.set(name, lineNo);
79
- }
80
- continue;
81
- }
82
-
83
- const state = line.match(stateDeclPattern);
84
- if (state) {
85
- const name = state[2];
86
- const first = declaredState.get(name);
87
- if (first !== undefined) {
88
- diagnostics.push(
89
- toDiag(
90
- "warning",
91
- lineNo,
92
- Math.max(1, line.indexOf(name) + 1),
93
- "FS_DUP_STATE",
94
- `Duplicate reactive/state declaration "${name}" (first seen on line ${first})`,
95
- "Rename one declaration to avoid shadowing confusion.",
96
- ),
97
- );
98
- } else {
99
- declaredState.set(name, lineNo);
100
- }
101
- continue;
102
- }
103
-
104
- if (reactiveStartPattern.test(line)) {
105
- diagnostics.push(
106
- toDiag(
107
- "warning",
108
- lineNo,
109
- Math.max(1, line.indexOf("~") + 1),
110
- "FS_BAD_REACTIVE",
111
- "Reactive declaration expected format: ~name = value",
112
- "Use: ~count = 0",
113
- ),
114
- );
115
- }
116
-
117
- if (stateStartPattern.test(line) && !stateDeclPattern.test(line)) {
118
- diagnostics.push(
119
- toDiag(
120
- "warning",
121
- lineNo,
122
- Math.max(1, line.indexOf("state") + 1),
123
- "FS_BAD_STATE",
124
- "State declaration expected format: state name = value",
125
- "Use: state count = 0",
126
- ),
127
- );
128
- }
129
-
130
- if (emptyImportClausePattern.test(line)) {
131
- diagnostics.push(
132
- toDiag(
133
- "error",
134
- lineNo,
135
- Math.max(1, line.indexOf("import") + 1),
136
- "FS_EMPTY_IMPORT",
137
- "Empty import clause is invalid.",
138
- 'Remove import or import at least one symbol: import { x } from "./mod.fs"',
139
- ),
140
- );
141
- }
142
-
143
- if (invalidFnPattern.test(line)) {
144
- diagnostics.push(
145
- toDiag(
146
- "warning",
147
- lineNo,
148
- Math.max(1, line.indexOf("fn") + 1),
149
- "FS_BAD_FN",
150
- 'Function shorthand expected format: fn name(args) { ... }',
151
- "Use: fn run(input) { ... }",
152
- ),
153
- );
154
- }
155
-
156
- if (isFs && relativeJsImportPattern.test(line)) {
157
- diagnostics.push(
158
- toDiag(
159
- "warning",
160
- lineNo,
161
- Math.max(1, line.indexOf("from") + 1),
162
- "FS_RELATIVE_JS_IMPORT",
163
- "Relative .js/.ts import inside .fs can hinder migration portability.",
164
- "Prefer extensionless imports or .fs extension for local modules.",
165
- ),
166
- );
167
- }
168
- }
169
-
170
- return diagnostics;
171
- }
172
-
173
- export function formatFastScriptDiagnostics(
174
- diagnostics,
175
- { filename = "input.fs", includeWarnings = true } = {},
176
- ) {
177
- const filtered = includeWarnings ? diagnostics : diagnostics.filter((d) => d.severity === "error");
178
- if (filtered.length === 0) return "";
179
- const lines = filtered.map((d) => {
180
- const sev = d.severity.toUpperCase();
181
- const fix = d.fix ? `\n fix: ${d.fix}` : "";
182
- return `[${sev}] ${d.code} ${filename}:${d.line}:${d.col}\n ${d.message}${fix}`;
1
+ import { compileFastScript } from "./fs-parser.mjs";
2
+
3
+ export function normalizeFastScript(source, options = {}) {
4
+ const { code } = compileFastScript(source, {
5
+ file: options.file || "",
6
+ mode: options.mode || "lenient",
7
+ recover: options.recover !== false,
8
+ inlineSourceMap: options.sourceMap === "inline",
183
9
  });
184
- return lines.join("\n");
185
- }
186
-
187
- export function createFastScriptDiagnosticError(
188
- diagnostics,
189
- { filename = "input.fs", includeWarnings = false } = {},
190
- ) {
191
- const printable = formatFastScriptDiagnostics(diagnostics, { filename, includeWarnings });
192
- const error = new Error(printable || `FastScript diagnostics failed for ${filename}`);
193
- error.code = "FASTSCRIPT_DIAGNOSTICS";
194
- error.diagnostics = diagnostics;
195
- return error;
196
- }
197
-
198
- export function normalizeFastScriptWithTelemetry(source, { filename = "input.fs", strict = false } = {}) {
199
- const startedAt = nowMs();
200
- const diagnostics = analyzeFastScriptSource(source, { filename });
201
- const errors = diagnostics.filter((d) => d.severity === "error");
202
- if (strict && errors.length > 0) {
203
- throw createFastScriptDiagnosticError(errors, { filename, includeWarnings: false });
204
- }
205
-
206
- const transformStats = {
207
- reactiveToLet: 0,
208
- stateToLet: 0,
209
- fnToFunction: 0,
210
- };
211
- const code = normalizeFastScriptInternal(source, transformStats);
212
- const durationMs = nowMs() - startedAt;
213
-
214
- return {
215
- code,
216
- diagnostics,
217
- stats: {
218
- lineCount: source.split(/\r?\n/).length,
219
- durationMs,
220
- ...transformStats,
221
- },
222
- };
10
+ return code;
223
11
  }
224
12
 
225
- export function normalizeFastScript(source) {
226
- return normalizeFastScriptInternal(source, {
227
- reactiveToLet: 0,
228
- stateToLet: 0,
229
- fnToFunction: 0,
13
+ export function normalizeFastScriptWithMetadata(source, options = {}) {
14
+ return compileFastScript(source, {
15
+ file: options.file || "",
16
+ mode: options.mode || "lenient",
17
+ recover: options.recover !== false,
18
+ inlineSourceMap: options.sourceMap === "inline",
230
19
  });
231
20
  }
232
21
 
@@ -253,52 +42,76 @@ export function stripTypeScriptHints(source) {
253
42
 
254
43
  if (/^\s*interface\s+[A-Za-z_$][\w$]*\s*[{]/.test(next) || /^\s*enum\s+[A-Za-z_$][\w$]*\s*[{]/.test(next)) {
255
44
  out.push(`// ${next.trim()} (removed by fastscript migrate)`);
45
+ skippingBlock = true;
256
46
  const opens = (next.match(/{/g) || []).length;
257
47
  const closes = (next.match(/}/g) || []).length;
258
- const depth = opens - closes;
259
- if (depth > 0) {
260
- skippingBlock = true;
261
- blockDepth = depth;
262
- }
48
+ blockDepth = Math.max(1, opens - closes);
263
49
  continue;
264
50
  }
265
51
 
266
52
  if (/^\s*type\s+[A-Za-z_$][\w$]*\s*=/.test(next)) {
267
53
  out.push(`// ${next.trim()} (removed by fastscript migrate)`);
268
54
  if (!next.includes(";") && next.includes("{")) {
55
+ skippingBlock = true;
56
+ const opens = (next.match(/{/g) || []).length;
57
+ const closes = (next.match(/}/g) || []).length;
58
+ blockDepth = Math.max(1, opens - closes);
59
+ }
60
+ continue;
61
+ }
62
+
63
+ if (/^\s*declare\s+(global|module|namespace)\b/.test(next)) {
64
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
65
+ if (next.includes("{")) {
66
+ skippingBlock = true;
269
67
  const opens = (next.match(/{/g) || []).length;
270
68
  const closes = (next.match(/}/g) || []).length;
271
- const depth = opens - closes;
272
- if (depth > 0) {
273
- skippingBlock = true;
274
- blockDepth = depth;
275
- }
69
+ blockDepth = Math.max(1, opens - closes);
276
70
  }
277
71
  continue;
278
72
  }
279
73
 
74
+ if (/^\s*declare\s+/.test(next)) {
75
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
76
+ continue;
77
+ }
78
+
280
79
  next = next.replace(/\bimport\s+type\b/g, "import");
281
80
  next = next.replace(/\bexport\s+type\b/g, "export");
81
+ next = next.replace(/\btype\s+([A-Za-z_$][\w$]*)\s+as\s+/g, "$1 as ");
82
+ next = next.replace(/\b(?:public|private|protected|readonly|declare|override|abstract)\s+/g, "");
83
+ next = next.replace(/\s+implements\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]+/g, "");
84
+ next = next.replace(/([A-Za-z_$][\w$]*)!([?:;=,\)])/g, "$1$2");
282
85
 
283
86
  next = next.replace(
284
87
  /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*:\s*([^=;]+)([=;].*)$/,
285
88
  "$1$2 $3 $5",
286
89
  );
90
+ next = next.replace(
91
+ /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*=\s*/,
92
+ "$1$2 $3 = ",
93
+ );
287
94
 
288
- if (/\bfunction\b/.test(next) || /\)\s*=>/.test(next)) {
95
+ if (/\bfunction\b/.test(next) || /\bfn\b/.test(next) || /\)\s*=>/.test(next)) {
289
96
  next = next.replace(/\(([^)]*)\)/, (_, params) => {
290
97
  const cleaned = params.replace(
291
- /([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$<>\[\]\|& ?. ]*)/g,
98
+ /([A-Za-z_$][\w$]*)(\?)?\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)/g,
292
99
  "$1",
293
100
  );
294
101
  return `(${cleaned})`;
295
102
  });
296
- next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|& ?. ]*)\s*\{/g, ") {");
103
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*\{/g, ") {");
104
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*=>/g, ") =>");
297
105
  next = next.replace(/\bfunction\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "function $1(");
106
+ next = next.replace(/\bfn\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "fn $1(");
107
+ next = next.replace(/=\s*async\s*<[^>]+>\s*\(/g, "= async (");
108
+ next = next.replace(/=\s*<[^>]+>\s*\(/g, "= (");
298
109
  }
299
110
 
300
111
  next = next.replace(/^\s*<([A-Za-z_$][\w$,\s]*)>\s*\(/, "(");
301
112
  next = next.replace(/\)\s*=>\s*<[A-Za-z_$][\w$<>\[\]\|&, ?.]*>/g, ") =>");
113
+ next = next.replace(/\s+as\s+const\b/g, "");
114
+ next = next.replace(/\s+as\s+[A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*/g, "");
302
115
  next = next.replace(/\sas\s+const\b/g, "");
303
116
  next = next.replace(/\s+satisfies\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]*/g, "");
304
117
  out.push(next);
@@ -0,0 +1 @@
1
+ export * from "@fastscript/core-private/parser";