fastscript 2.0.0 → 3.0.1

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.
@@ -0,0 +1,100 @@
1
+ import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync, existsSync } from "node:fs";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { analyzeFastScript } from "./fs-diagnostics.mjs";
4
+
5
+ function walk(dir) {
6
+ const out = [];
7
+ if (!existsSync(dir)) return out;
8
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
9
+ const full = join(dir, entry.name);
10
+ if (entry.isDirectory()) {
11
+ if (["node_modules", ".git", "dist", ".fastscript"].includes(entry.name)) continue;
12
+ out.push(...walk(full));
13
+ } else if (entry.isFile()) {
14
+ out.push(full);
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+
20
+ function parseArgs(args = []) {
21
+ const options = {
22
+ path: resolve("app"),
23
+ mode: "report",
24
+ out: resolve(".fastscript", "diagnostics-report.json"),
25
+ };
26
+
27
+ for (let i = 0; i < args.length; i += 1) {
28
+ const arg = args[i];
29
+ if (arg === "--path") {
30
+ options.path = resolve(args[i + 1] || options.path);
31
+ i += 1;
32
+ continue;
33
+ }
34
+ if (arg === "--out") {
35
+ options.out = resolve(args[i + 1] || options.out);
36
+ i += 1;
37
+ continue;
38
+ }
39
+ if (arg === "--mode") {
40
+ const next = (args[i + 1] || "report").toLowerCase();
41
+ options.mode = next === "fail" ? "fail" : "report";
42
+ i += 1;
43
+ }
44
+ }
45
+
46
+ return options;
47
+ }
48
+
49
+ export async function runDiagnostics(args = []) {
50
+ const options = parseArgs(args);
51
+ const files = walk(options.path).filter((file) => file.endsWith(".fs"));
52
+ const diagnostics = [];
53
+
54
+ for (const file of files) {
55
+ const source = readFileSync(file, "utf8");
56
+ const issues = analyzeFastScript(source, { file, mode: "lenient" });
57
+ for (const issue of issues) {
58
+ diagnostics.push({
59
+ file: normalizePath(file),
60
+ code: issue.code,
61
+ severity: issue.severity,
62
+ message: issue.message,
63
+ line: issue.line,
64
+ column: issue.column,
65
+ });
66
+ }
67
+ }
68
+
69
+ const summary = diagnostics.reduce(
70
+ (acc, item) => {
71
+ if (item.severity === "warning") acc.warnings += 1;
72
+ else acc.errors += 1;
73
+ acc.byCode[item.code] = (acc.byCode[item.code] || 0) + 1;
74
+ return acc;
75
+ },
76
+ { errors: 0, warnings: 0, byCode: {} },
77
+ );
78
+
79
+ const report = {
80
+ generatedAt: new Date().toISOString(),
81
+ root: normalizePath(options.path),
82
+ filesScanned: files.length,
83
+ summary,
84
+ diagnostics,
85
+ };
86
+
87
+ mkdirSync(dirname(options.out), { recursive: true });
88
+ writeFileSync(options.out, `${JSON.stringify(report, null, 2)}\n`, "utf8");
89
+
90
+ if (options.mode === "fail" && summary.errors > 0) {
91
+ throw new Error(`diagnostics failed: ${summary.errors} blocking issue(s)`);
92
+ }
93
+
94
+ console.log(`diagnostics complete: files=${files.length}, errors=${summary.errors}, warnings=${summary.warnings}`);
95
+ console.log(`diagnostics report: ${normalizePath(relative(resolve("."), options.out))}`);
96
+ }
97
+
98
+ function normalizePath(path) {
99
+ return String(path || "").replace(/\\/g, "/");
100
+ }
@@ -16,8 +16,8 @@ export const FS_ERROR_CODES = Object.freeze({
16
16
  },
17
17
  FS1004: {
18
18
  severity: "error",
19
- message: "Type declarations are not valid runtime FastScript syntax.",
20
- hint: "Move `type`, `interface`, and `enum` definitions to `.d.ts` files or remove them from `.fs` files.",
19
+ message: "Legacy compatibility frontend conflict.",
20
+ hint: "Ordinary TS type-only syntax in `.fs` should parse. If this appears, treat it as a FastScript compatibility bug.",
21
21
  },
22
22
  FS1005: {
23
23
  severity: "error",
@@ -1,7 +1,8 @@
1
1
  import { compileFastScript } from "./fs-parser.mjs";
2
2
 
3
3
  export function normalizeFastScript(source, options = {}) {
4
- const { code } = compileFastScript(source, {
4
+ const prepared = stripTypeScriptHints(String(source ?? ""));
5
+ const { code } = compileFastScript(prepared, {
5
6
  file: options.file || "",
6
7
  mode: options.mode || "lenient",
7
8
  recover: options.recover !== false,
@@ -11,7 +12,8 @@ export function normalizeFastScript(source, options = {}) {
11
12
  }
12
13
 
13
14
  export function normalizeFastScriptWithMetadata(source, options = {}) {
14
- return compileFastScript(source, {
15
+ const prepared = stripTypeScriptHints(String(source ?? ""));
16
+ return compileFastScript(prepared, {
15
17
  file: options.file || "",
16
18
  mode: options.mode || "lenient",
17
19
  recover: options.recover !== false,
@@ -40,7 +42,7 @@ export function stripTypeScriptHints(source) {
40
42
  continue;
41
43
  }
42
44
 
43
- if (/^\s*interface\s+[A-Za-z_$][\w$]*\s*[{]/.test(next) || /^\s*enum\s+[A-Za-z_$][\w$]*\s*[{]/.test(next)) {
45
+ if (/^\s*interface\s+[A-Za-z_$][\w$]*(?:\s*<[^>]+>)?\s*[{]/.test(next) || /^\s*enum\s+[A-Za-z_$][\w$]*\s*[{]/.test(next)) {
44
46
  out.push(`// ${next.trim()} (removed by fastscript migrate)`);
45
47
  skippingBlock = true;
46
48
  const opens = (next.match(/{/g) || []).length;
@@ -49,7 +51,7 @@ export function stripTypeScriptHints(source) {
49
51
  continue;
50
52
  }
51
53
 
52
- if (/^\s*type\s+[A-Za-z_$][\w$]*\s*=/.test(next)) {
54
+ if (/^\s*type\s+[A-Za-z_$][\w$]*(?:\s*<[^>]+>)?\s*=/.test(next)) {
53
55
  out.push(`// ${next.trim()} (removed by fastscript migrate)`);
54
56
  if (!next.includes(";") && next.includes("{")) {
55
57
  skippingBlock = true;
@@ -60,28 +62,58 @@ export function stripTypeScriptHints(source) {
60
62
  continue;
61
63
  }
62
64
 
65
+ if (/^\s*declare\s+(global|module|namespace)\b/.test(next)) {
66
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
67
+ if (next.includes("{")) {
68
+ skippingBlock = true;
69
+ const opens = (next.match(/{/g) || []).length;
70
+ const closes = (next.match(/}/g) || []).length;
71
+ blockDepth = Math.max(1, opens - closes);
72
+ }
73
+ continue;
74
+ }
75
+
76
+ if (/^\s*declare\s+/.test(next)) {
77
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
78
+ continue;
79
+ }
80
+
63
81
  next = next.replace(/\bimport\s+type\b/g, "import");
64
82
  next = next.replace(/\bexport\s+type\b/g, "export");
83
+ next = next.replace(/\btype\s+([A-Za-z_$][\w$]*)\s+as\s+/g, "$1 as ");
84
+ next = next.replace(/\b(?:public|private|protected|readonly|declare|override|abstract)\s+/g, "");
85
+ next = next.replace(/\s+implements\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]+/g, "");
86
+ next = next.replace(/([A-Za-z_$][\w$]*)!([?:;=,\)])/g, "$1$2");
65
87
 
66
88
  next = next.replace(
67
89
  /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*:\s*([^=;]+)([=;].*)$/,
68
90
  "$1$2 $3 $5",
69
91
  );
92
+ next = next.replace(
93
+ /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*=\s*/,
94
+ "$1$2 $3 = ",
95
+ );
70
96
 
71
- if (/\bfunction\b/.test(next) || /\)\s*=>/.test(next)) {
97
+ if (/\bfunction\b/.test(next) || /\bfn\b/.test(next) || /\)\s*=>/.test(next)) {
72
98
  next = next.replace(/\(([^)]*)\)/, (_, params) => {
73
99
  const cleaned = params.replace(
74
- /([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)/g,
100
+ /([A-Za-z_$][\w$]*)(\?)?\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)/g,
75
101
  "$1",
76
102
  );
77
103
  return `(${cleaned})`;
78
104
  });
79
- next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)\s*\{/g, ") {");
105
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*\{/g, ") {");
106
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*=>/g, ") =>");
80
107
  next = next.replace(/\bfunction\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "function $1(");
108
+ next = next.replace(/\bfn\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "fn $1(");
109
+ next = next.replace(/=\s*async\s*<[^>]+>\s*\(/g, "= async (");
110
+ next = next.replace(/=\s*<[^>]+>\s*\(/g, "= (");
81
111
  }
82
112
 
83
113
  next = next.replace(/^\s*<([A-Za-z_$][\w$,\s]*)>\s*\(/, "(");
84
114
  next = next.replace(/\)\s*=>\s*<[A-Za-z_$][\w$<>\[\]\|&, ?.]*>/g, ") =>");
115
+ next = next.replace(/\s+as\s+const\b/g, "");
116
+ next = next.replace(/\s+as\s+[A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*/g, "");
85
117
  next = next.replace(/\sas\s+const\b/g, "");
86
118
  next = next.replace(/\s+satisfies\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]*/g, "");
87
119
  out.push(next);