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.
- package/CHANGELOG.md +38 -7
- package/LICENSE +33 -21
- package/README.md +605 -73
- package/node_modules/@fastscript/core-private/BOUNDARY.json +15 -0
- package/node_modules/@fastscript/core-private/README.md +5 -0
- package/node_modules/@fastscript/core-private/package.json +34 -0
- package/node_modules/@fastscript/core-private/src/asset-optimizer.mjs +67 -0
- package/node_modules/@fastscript/core-private/src/audit-log.mjs +50 -0
- package/node_modules/@fastscript/core-private/src/auth-flows.mjs +29 -0
- package/node_modules/@fastscript/core-private/src/auth.mjs +115 -0
- package/node_modules/@fastscript/core-private/src/bench.mjs +45 -0
- package/node_modules/@fastscript/core-private/src/build.mjs +670 -0
- package/node_modules/@fastscript/core-private/src/cache.mjs +248 -0
- package/node_modules/@fastscript/core-private/src/check.mjs +22 -0
- package/node_modules/@fastscript/core-private/src/cli.mjs +95 -0
- package/node_modules/@fastscript/core-private/src/compat.mjs +128 -0
- package/node_modules/@fastscript/core-private/src/create.mjs +278 -0
- package/node_modules/@fastscript/core-private/src/csp.mjs +26 -0
- package/node_modules/@fastscript/core-private/src/db-cli.mjs +185 -0
- package/node_modules/@fastscript/core-private/src/db-postgres-collection.mjs +110 -0
- package/node_modules/@fastscript/core-private/src/db-postgres.mjs +40 -0
- package/node_modules/@fastscript/core-private/src/db.mjs +103 -0
- package/node_modules/@fastscript/core-private/src/deploy.mjs +662 -0
- package/node_modules/@fastscript/core-private/src/dev.mjs +5 -0
- package/node_modules/@fastscript/core-private/src/docs-search.mjs +35 -0
- package/node_modules/@fastscript/core-private/src/env.mjs +118 -0
- package/node_modules/@fastscript/core-private/src/export.mjs +83 -0
- package/node_modules/@fastscript/core-private/src/fs-diagnostics.mjs +70 -0
- package/node_modules/@fastscript/core-private/src/fs-error-codes.mjs +141 -0
- package/node_modules/@fastscript/core-private/src/fs-formatter.mjs +66 -0
- package/node_modules/@fastscript/core-private/src/fs-linter.mjs +274 -0
- package/node_modules/@fastscript/core-private/src/fs-normalize.mjs +121 -0
- package/node_modules/@fastscript/core-private/src/fs-parser.mjs +1120 -0
- package/node_modules/@fastscript/core-private/src/generated/docs-search-index.mjs +3182 -0
- package/node_modules/@fastscript/core-private/src/i18n.mjs +25 -0
- package/node_modules/@fastscript/core-private/src/interop.mjs +16 -0
- package/node_modules/@fastscript/core-private/src/jobs.mjs +378 -0
- package/node_modules/@fastscript/core-private/src/logger.mjs +27 -0
- package/node_modules/@fastscript/core-private/src/metrics.mjs +45 -0
- package/node_modules/@fastscript/core-private/src/middleware.mjs +14 -0
- package/node_modules/@fastscript/core-private/src/migrate.mjs +81 -0
- package/node_modules/@fastscript/core-private/src/migration-wizard.mjs +16 -0
- package/node_modules/@fastscript/core-private/src/module-loader.mjs +46 -0
- package/node_modules/@fastscript/core-private/src/oauth-providers.mjs +103 -0
- package/node_modules/@fastscript/core-private/src/observability.mjs +21 -0
- package/node_modules/@fastscript/core-private/src/plugins.mjs +194 -0
- package/node_modules/@fastscript/core-private/src/retention.mjs +57 -0
- package/node_modules/@fastscript/core-private/src/routes.mjs +178 -0
- package/node_modules/@fastscript/core-private/src/scheduler.mjs +104 -0
- package/node_modules/@fastscript/core-private/src/security.mjs +233 -0
- package/node_modules/@fastscript/core-private/src/server-runtime.mjs +849 -0
- package/node_modules/@fastscript/core-private/src/serverless-handler.mjs +20 -0
- package/node_modules/@fastscript/core-private/src/session-policy.mjs +38 -0
- package/node_modules/@fastscript/core-private/src/start.mjs +10 -0
- package/node_modules/@fastscript/core-private/src/storage.mjs +155 -0
- package/node_modules/@fastscript/core-private/src/style-primitives.mjs +538 -0
- package/node_modules/@fastscript/core-private/src/style-system.mjs +461 -0
- package/node_modules/@fastscript/core-private/src/tenant.mjs +55 -0
- package/node_modules/@fastscript/core-private/src/typecheck.mjs +1466 -0
- package/node_modules/@fastscript/core-private/src/validate.mjs +22 -0
- package/node_modules/@fastscript/core-private/src/validation.mjs +88 -0
- package/node_modules/@fastscript/core-private/src/webhook.mjs +81 -0
- package/node_modules/@fastscript/core-private/src/worker.mjs +24 -0
- package/package.json +108 -14
- package/src/asset-optimizer.mjs +67 -0
- package/src/audit-log.mjs +50 -0
- package/src/auth.mjs +1 -115
- package/src/bench.mjs +20 -7
- package/src/benchmark-discipline.mjs +39 -0
- package/src/build.mjs +1 -234
- package/src/cache.mjs +210 -20
- package/src/cli.mjs +65 -6
- package/src/compat.mjs +8 -10
- package/src/conversion-manifest.mjs +101 -0
- package/src/create.mjs +71 -17
- package/src/csp.mjs +26 -0
- package/src/db-cli.mjs +152 -8
- package/src/db-postgres-collection.mjs +110 -0
- package/src/deploy.mjs +1 -65
- package/src/diagnostics.mjs +100 -0
- package/src/docs-search.mjs +35 -0
- package/src/env.mjs +34 -5
- package/src/fs-diagnostics.mjs +70 -0
- package/src/fs-error-codes.mjs +126 -0
- package/src/fs-formatter.mjs +66 -0
- package/src/fs-linter.mjs +274 -0
- package/src/fs-normalize.mjs +52 -239
- package/src/fs-parser.mjs +1 -0
- package/src/generated/docs-search-index.mjs +3591 -0
- package/src/i18n.mjs +25 -0
- package/src/jobs.mjs +283 -32
- package/src/metrics.mjs +45 -0
- package/src/migrate-rollback.mjs +144 -0
- package/src/migrate.mjs +1275 -47
- package/src/migration-wizard.mjs +42 -0
- package/src/module-loader.mjs +22 -11
- package/src/oauth-providers.mjs +103 -0
- package/src/permissions-cli.mjs +112 -0
- package/src/plugins.mjs +194 -0
- package/src/profile.mjs +95 -0
- package/src/regression-guard.mjs +245 -0
- package/src/retention.mjs +57 -0
- package/src/routes.mjs +178 -0
- package/src/runtime-permissions.mjs +299 -0
- package/src/scheduler.mjs +104 -0
- package/src/security.mjs +197 -19
- package/src/server-runtime.mjs +1 -339
- package/src/serverless-handler.mjs +20 -0
- package/src/session-policy.mjs +38 -0
- package/src/storage.mjs +1 -56
- package/src/style-system.mjs +461 -0
- package/src/tenant.mjs +55 -0
- package/src/trace.mjs +95 -0
- package/src/typecheck.mjs +1 -0
- package/src/validate.mjs +13 -1
- package/src/validation.mjs +14 -5
- package/src/webhook.mjs +1 -71
- package/src/worker.mjs +23 -4
- 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
|
+
}
|
package/src/fs-normalize.mjs
CHANGED
|
@@ -1,232 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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$]*)
|
|
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$<>\[\]
|
|
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";
|