@mindees/compiler 0.5.0 → 0.7.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/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/perf-lint.d.ts +17 -0
- package/dist/perf-lint.d.ts.map +1 -0
- package/dist/perf-lint.js +269 -0
- package/dist/perf-lint.js.map +1 -0
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +9 -2
- package/dist/transform.js.map +1 -1
- package/dist/typecheck.d.ts +1 -0
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +1 -1
- package/dist/typecheck.js.map +1 -1
- package/dist/types.d.ts +7 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NativeTarget, compileToNative } from "./aot.js";
|
|
2
|
+
import { PerfLintOptions, perfLint } from "./perf-lint.js";
|
|
2
3
|
import { CompileOptions, CompileResult, CompileStats, Diagnostic, DiagnosticSeverity, MdcPlugin, SourcePosition } from "./types.js";
|
|
3
4
|
import { STATIC_MARKER, createFlattenTransformer } from "./flatten.js";
|
|
4
5
|
import { GenerateRouteModuleOptions, RouteEntry, RouteManifest, buildRouteManifest, chunkName, fileToRoute, generateRouteModule } from "./routes.js";
|
|
@@ -10,7 +11,7 @@ import { Maturity, NotImplementedError, PackageInfo, notImplemented } from "@min
|
|
|
10
11
|
/** The npm package name. */
|
|
11
12
|
declare const name = "@mindees/compiler";
|
|
12
13
|
/** The package version. All `@mindees/*` packages share one locked version line. */
|
|
13
|
-
declare const VERSION = "0.
|
|
14
|
+
declare const VERSION = "0.7.0";
|
|
14
15
|
/**
|
|
15
16
|
* Current maturity. The build-time optimizer — type-check gate, TSX→createElement
|
|
16
17
|
* transform, tree-flattening, per-route manifest, plugin API — is implemented
|
|
@@ -25,5 +26,5 @@ declare const maturity: Maturity;
|
|
|
25
26
|
*/
|
|
26
27
|
declare const info: PackageInfo;
|
|
27
28
|
//#endregion
|
|
28
|
-
export { type CompileOptions, type CompileResult, type CompileStats, type Diagnostic, type DiagnosticSeverity, type GenerateRouteModuleOptions, type Maturity, type MdcPlugin, type NativeTarget, NotImplementedError, type PackageInfo, type RouteEntry, type RouteManifest, STATIC_MARKER, type SourcePosition, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, generateRouteModule, hasErrors, info, maturity, name, notImplemented, typecheck };
|
|
29
|
+
export { type CompileOptions, type CompileResult, type CompileStats, type Diagnostic, type DiagnosticSeverity, type GenerateRouteModuleOptions, type Maturity, type MdcPlugin, type NativeTarget, NotImplementedError, type PackageInfo, type PerfLintOptions, type RouteEntry, type RouteManifest, STATIC_MARKER, type SourcePosition, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, generateRouteModule, hasErrors, info, maturity, name, notImplemented, perfLint, typecheck };
|
|
29
30
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;cAmCa,IAAA;;cAGA,OAAA;AAQb;;;;AAAgD;AAOhD;AAPA,cAAa,QAAA,EAAU,QAAyB;;;AAOoC;;;cAAvE,IAAA,EAAM,WAAiE"}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { compileToNative } from "./aot.js";
|
|
2
2
|
import { STATIC_MARKER, createFlattenTransformer } from "./flatten.js";
|
|
3
|
-
import { buildRouteManifest, chunkName, fileToRoute, generateRouteModule } from "./routes.js";
|
|
4
3
|
import { hasErrors, typecheck } from "./typecheck.js";
|
|
4
|
+
import { perfLint } from "./perf-lint.js";
|
|
5
|
+
import { buildRouteManifest, chunkName, fileToRoute, generateRouteModule } from "./routes.js";
|
|
5
6
|
import { compile, compileChecked } from "./transform.js";
|
|
6
7
|
import { NotImplementedError, notImplemented } from "@mindees/core";
|
|
7
8
|
//#region src/index.ts
|
|
8
9
|
/** The npm package name. */
|
|
9
10
|
const name = "@mindees/compiler";
|
|
10
11
|
/** The package version. All `@mindees/*` packages share one locked version line. */
|
|
11
|
-
const VERSION = "0.
|
|
12
|
+
const VERSION = "0.7.0";
|
|
12
13
|
/**
|
|
13
14
|
* Current maturity. The build-time optimizer — type-check gate, TSX→createElement
|
|
14
15
|
* transform, tree-flattening, per-route manifest, plugin API — is implemented
|
|
@@ -27,6 +28,6 @@ const info = Object.freeze({
|
|
|
27
28
|
maturity
|
|
28
29
|
});
|
|
29
30
|
//#endregion
|
|
30
|
-
export { NotImplementedError, STATIC_MARKER, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, generateRouteModule, hasErrors, info, maturity, name, notImplemented, typecheck };
|
|
31
|
+
export { NotImplementedError, STATIC_MARKER, VERSION, buildRouteManifest, chunkName, compile, compileChecked, compileToNative, createFlattenTransformer, fileToRoute, generateRouteModule, hasErrors, info, maturity, name, notImplemented, perfLint, typecheck };
|
|
31
32
|
|
|
32
33
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from '@mindees/core'\nimport { NotImplementedError, notImplemented } from '@mindees/core'\n\n/** TS → native AOT (research track). */\nexport { compileToNative, type NativeTarget } from './aot'\n/** Tree-flattening optimizer pass. */\nexport { createFlattenTransformer, STATIC_MARKER } from './flatten'\n/** Per-route code-splitting manifest + file-based route codegen. */\nexport {\n buildRouteManifest,\n chunkName,\n fileToRoute,\n type GenerateRouteModuleOptions,\n generateRouteModule,\n type RouteEntry,\n type RouteManifest,\n} from './routes'\n/** Compile pipeline (TSX → optimized JS). */\nexport { compile, compileChecked } from './transform'\n/** The type-check gate. */\nexport { hasErrors, typecheck } from './typecheck'\n/** Shared types. */\nexport type {\n CompileOptions,\n CompileResult,\n CompileStats,\n Diagnostic,\n DiagnosticSeverity,\n MdcPlugin,\n SourcePosition,\n} from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/compiler'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from '@mindees/core'\nimport { NotImplementedError, notImplemented } from '@mindees/core'\n\n/** TS → native AOT (research track). */\nexport { compileToNative, type NativeTarget } from './aot'\n/** Tree-flattening optimizer pass. */\nexport { createFlattenTransformer, STATIC_MARKER } from './flatten'\n/** Build-time perf-lint (opt-in via `compileChecked(src, { perf: true })`). */\nexport { type PerfLintOptions, perfLint } from './perf-lint'\n/** Per-route code-splitting manifest + file-based route codegen. */\nexport {\n buildRouteManifest,\n chunkName,\n fileToRoute,\n type GenerateRouteModuleOptions,\n generateRouteModule,\n type RouteEntry,\n type RouteManifest,\n} from './routes'\n/** Compile pipeline (TSX → optimized JS). */\nexport { compile, compileChecked } from './transform'\n/** The type-check gate. */\nexport { hasErrors, typecheck } from './typecheck'\n/** Shared types. */\nexport type {\n CompileOptions,\n CompileResult,\n CompileStats,\n Diagnostic,\n DiagnosticSeverity,\n MdcPlugin,\n SourcePosition,\n} from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/compiler'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.7.0'\n\n/**\n * Current maturity. The build-time optimizer — type-check gate, TSX→createElement\n * transform, tree-flattening, per-route manifest, plugin API — is implemented\n * and tested on the TypeScript Compiler API. TS→native AOT is a research track\n * (throws `NotImplementedError`); the working path is TS → optimized JS.\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n\nexport type { Maturity, PackageInfo }\nexport { NotImplementedError, notImplemented }\n"],"mappings":";;;;;;;;;AAmCA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;AAQvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Diagnostic } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/perf-lint.d.ts
|
|
4
|
+
/** Per-call configuration for {@link perfLint}. */
|
|
5
|
+
interface PerfLintOptions {
|
|
6
|
+
/** Turn individual rules on/off (default: all on except `MDC_PERF_007`). */
|
|
7
|
+
readonly rules?: Record<string, 'off' | 'warning'>;
|
|
8
|
+
/** Element count at/above which `MDC_PERF_007` fires (default 50). */
|
|
9
|
+
readonly listSizeThreshold?: number;
|
|
10
|
+
/** Identifiers treated as keyed list builders (default For/keyedRegion/List/createList/SectionList/createSectionList). */
|
|
11
|
+
readonly keyedNames?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
/** Run the perf-lint over one module's source; returns `warning` diagnostics (possibly empty). */
|
|
14
|
+
declare function perfLint(source: string, fileName?: string, options?: PerfLintOptions): Diagnostic[];
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PerfLintOptions, perfLint };
|
|
17
|
+
//# sourceMappingURL=perf-lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-lint.d.ts","names":[],"sources":["../src/perf-lint.ts"],"mappings":";;;AAmDA;AAAA,UAjCiB,eAAA;;WAEN,KAAA,GAAQ,MAAM;EAgCvB;EAAA,SA9BS,iBAAA;EAgCA;EAAA,SA9BA,UAAA;AAAA;;iBA2BK,QAAA,CACd,MAAA,UACA,QAAA,WACA,OAAA,GAAS,eAAA,GACR,UAAU"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { scriptKindForFile } from "./typecheck.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
//#region src/perf-lint.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build-time **perf-lint** — an opt-in pass that flags real performance footguns in the
|
|
6
|
+
* fine-grained reactive + Helix render model, as `warning` {@link Diagnostic}s (it NEVER blocks the
|
|
7
|
+
* build). Honest by design: every rule reports a concrete structural fact and *why* it's slow in
|
|
8
|
+
* THIS model — no invented frame-time numbers. A diagnostic neither React Native nor Flutter ships.
|
|
9
|
+
*
|
|
10
|
+
* Enable via `compileChecked(src, { perf: true })` (or `{ perf: { rules, listSizeThreshold } }`).
|
|
11
|
+
* Suppress a finding with a leading `// mdc-perf-ignore` (all) or `// mdc-perf-ignore MDC_PERF_001`
|
|
12
|
+
* (one code) comment, or `rules: { MDC_PERF_001: 'off' }`.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_KEYED = [
|
|
17
|
+
"For",
|
|
18
|
+
"keyedRegion",
|
|
19
|
+
"List",
|
|
20
|
+
"createList",
|
|
21
|
+
"SectionList",
|
|
22
|
+
"createSectionList"
|
|
23
|
+
];
|
|
24
|
+
const OFF_BY_DEFAULT = new Set(["MDC_PERF_007"]);
|
|
25
|
+
/** A `const x = signal()|computed()|memo()|use<Capital>()` binds a reactive accessor. */
|
|
26
|
+
const ACCESSOR_FACTORY = /^(signal|computed|memo|use[A-Z])/;
|
|
27
|
+
const HEAVY_METHODS = new Set([
|
|
28
|
+
"map",
|
|
29
|
+
"filter",
|
|
30
|
+
"reduce",
|
|
31
|
+
"forEach",
|
|
32
|
+
"sort",
|
|
33
|
+
"flatMap",
|
|
34
|
+
"reduceRight"
|
|
35
|
+
]);
|
|
36
|
+
const SUBSCRIBE_CALLS = /(addEventListener|setInterval|setTimeout|subscribe|^on$|observe|addListener)/;
|
|
37
|
+
/** Run the perf-lint over one module's source; returns `warning` diagnostics (possibly empty). */
|
|
38
|
+
function perfLint(source, fileName = "module.tsx", options = {}) {
|
|
39
|
+
const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.ES2023, true, scriptKindForFile(fileName));
|
|
40
|
+
const keyed = new Set(options.keyedNames ?? DEFAULT_KEYED);
|
|
41
|
+
const threshold = options.listSizeThreshold ?? 50;
|
|
42
|
+
const out = [];
|
|
43
|
+
const isEnabled = (code) => {
|
|
44
|
+
const setting = options.rules?.[code];
|
|
45
|
+
if (setting) return setting !== "off";
|
|
46
|
+
return !OFF_BY_DEFAULT.has(code);
|
|
47
|
+
};
|
|
48
|
+
const accessors = /* @__PURE__ */ new Set();
|
|
49
|
+
const collectAccessors = (node) => {
|
|
50
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer)) {
|
|
51
|
+
const callee = node.initializer.expression;
|
|
52
|
+
const name = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : "";
|
|
53
|
+
if (ACCESSOR_FACTORY.test(name)) accessors.add(node.name.text);
|
|
54
|
+
}
|
|
55
|
+
ts.forEachChild(node, collectAccessors);
|
|
56
|
+
};
|
|
57
|
+
collectAccessors(sf);
|
|
58
|
+
const positionOf = (node) => {
|
|
59
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
60
|
+
return {
|
|
61
|
+
line: line + 1,
|
|
62
|
+
column: character + 1
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const sourceLines = source.split("\n");
|
|
66
|
+
const isSuppressed = (node, code) => {
|
|
67
|
+
const { line } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
68
|
+
for (const ln of [line, line - 1]) {
|
|
69
|
+
const m = /mdc-perf-ignore(?:\s+(MDC_PERF_\d+))?/.exec(sourceLines[ln] ?? "");
|
|
70
|
+
if (m && (!m[1] || m[1] === code)) return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
74
|
+
const emit = (node, code, message) => {
|
|
75
|
+
if (!isEnabled(code) || isSuppressed(node, code)) return;
|
|
76
|
+
out.push({
|
|
77
|
+
severity: "warning",
|
|
78
|
+
code,
|
|
79
|
+
message,
|
|
80
|
+
file: fileName,
|
|
81
|
+
position: positionOf(node)
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const unwrapArrow = (expr) => {
|
|
85
|
+
if (ts.isArrowFunction(expr) && !ts.isBlock(expr.body)) return expr.body;
|
|
86
|
+
return expr;
|
|
87
|
+
};
|
|
88
|
+
const returnsJsx = (fn) => {
|
|
89
|
+
if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return false;
|
|
90
|
+
let found = false;
|
|
91
|
+
const look = (n) => {
|
|
92
|
+
if (found) return;
|
|
93
|
+
if (ts.isJsxElement(n) || ts.isJsxFragment(n) || ts.isJsxSelfClosingElement(n)) {
|
|
94
|
+
found = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (n !== fn && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))) return;
|
|
98
|
+
ts.forEachChild(n, look);
|
|
99
|
+
};
|
|
100
|
+
look(fn.body);
|
|
101
|
+
return found;
|
|
102
|
+
};
|
|
103
|
+
const calleeName = (call) => ts.isIdentifier(call.expression) ? call.expression.text : "";
|
|
104
|
+
const propAccessName = (call) => ts.isPropertyAccessExpression(call.expression) ? call.expression.name.text : "";
|
|
105
|
+
/** Does the subtree contain ANY call expression? (A returned literal with no call is provably static.) */
|
|
106
|
+
const containsAnyCall = (root) => {
|
|
107
|
+
let found = false;
|
|
108
|
+
const look = (n) => {
|
|
109
|
+
if (found) return;
|
|
110
|
+
if (ts.isCallExpression(n)) {
|
|
111
|
+
found = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
ts.forEachChild(n, look);
|
|
115
|
+
};
|
|
116
|
+
look(root);
|
|
117
|
+
return found;
|
|
118
|
+
};
|
|
119
|
+
/** Does the subtree reactively read a known accessor (a zero-arg call `acc()`)? */
|
|
120
|
+
const readsAccessor = (root) => {
|
|
121
|
+
let found = false;
|
|
122
|
+
const look = (n) => {
|
|
123
|
+
if (found) return;
|
|
124
|
+
if (ts.isCallExpression(n) && n.arguments.length === 0 && ts.isIdentifier(n.expression) && accessors.has(n.expression.text)) {
|
|
125
|
+
found = true;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
ts.forEachChild(n, look);
|
|
129
|
+
};
|
|
130
|
+
look(root);
|
|
131
|
+
return found;
|
|
132
|
+
};
|
|
133
|
+
/** Does the subtree contain a heavy synchronous construct (loop / array-method chain / JSON)? */
|
|
134
|
+
const hasHeavyWork = (root) => {
|
|
135
|
+
let found = false;
|
|
136
|
+
const look = (n) => {
|
|
137
|
+
if (found) return;
|
|
138
|
+
if (ts.isForStatement(n) || ts.isForOfStatement(n) || ts.isForInStatement(n) || ts.isWhileStatement(n)) {
|
|
139
|
+
found = true;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (ts.isCallExpression(n) && HEAVY_METHODS.has(propAccessName(n))) {
|
|
143
|
+
found = true;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (ts.isCallExpression(n) && ts.isPropertyAccessExpression(n.expression) && ts.isIdentifier(n.expression.expression) && n.expression.expression.text === "JSON") {
|
|
147
|
+
found = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
ts.forEachChild(n, look);
|
|
151
|
+
};
|
|
152
|
+
look(root);
|
|
153
|
+
return found;
|
|
154
|
+
};
|
|
155
|
+
const ruleMapJsxChild = (node) => {
|
|
156
|
+
if (!ts.isJsxExpression(node) || !node.expression) return;
|
|
157
|
+
const parent = node.parent;
|
|
158
|
+
if (!parent || !(ts.isJsxElement(parent) || ts.isJsxFragment(parent))) return;
|
|
159
|
+
const expr = unwrapArrow(node.expression);
|
|
160
|
+
if (!ts.isCallExpression(expr) || propAccessName(expr) !== "map") return;
|
|
161
|
+
const cb = expr.arguments[0];
|
|
162
|
+
if (!cb || !returnsJsx(cb)) return;
|
|
163
|
+
emit(node, "MDC_PERF_001", "List rendered with a bare .map() — on any change this region re-mounts EVERY row (losing focus/scroll/state). Use For({ each, key, children }) (keyed: only the diff is created/moved/disposed) or List({...}) for large lists.");
|
|
164
|
+
};
|
|
165
|
+
const ruleMissingKey = (node) => {
|
|
166
|
+
if (!ts.isCallExpression(node)) return;
|
|
167
|
+
const name = calleeName(node);
|
|
168
|
+
if (!keyed.has(name)) return;
|
|
169
|
+
const arg = node.arguments[0];
|
|
170
|
+
if (!arg || !ts.isObjectLiteralExpression(arg)) return;
|
|
171
|
+
let hasKey = false;
|
|
172
|
+
let hasSpread = false;
|
|
173
|
+
let hasEach = false;
|
|
174
|
+
for (const p of arg.properties) {
|
|
175
|
+
if (ts.isSpreadAssignment(p)) hasSpread = true;
|
|
176
|
+
if ((ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && p.name && ts.isIdentifier(p.name)) {
|
|
177
|
+
if (p.name.text === "key") hasKey = true;
|
|
178
|
+
if (p.name.text === "each") hasEach = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (hasKey || hasSpread || !hasEach) return;
|
|
182
|
+
emit(node, "MDC_PERF_002", `${name}() has no \`key\` — it defaults to object identity, so freshly-built rows never match and every row is disposed + recreated (or throws on a duplicate primitive). Add key: (item) => item.id (a stable id).`);
|
|
183
|
+
};
|
|
184
|
+
const ruleHeavyEffect = (node) => {
|
|
185
|
+
if (!ts.isCallExpression(node) || calleeName(node) !== "effect") return;
|
|
186
|
+
if (node.arguments.length >= 2) return;
|
|
187
|
+
const fn = node.arguments[0];
|
|
188
|
+
if (!fn || !ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return;
|
|
189
|
+
if (hasHeavyWork(fn.body) && readsAccessor(fn.body)) emit(node, "MDC_PERF_003", "Heavy synchronous work in a default (sync-lane) effect — it runs inline on every dependency write, blocking the interaction. Move derived values into computed()/memo() (lazy + cached), or run heavy work on the deferred lane: effect(fn, { priority: 'normal' }).");
|
|
190
|
+
};
|
|
191
|
+
const ruleRepeatedReadInLoop = (node) => {
|
|
192
|
+
let body;
|
|
193
|
+
if (ts.isForStatement(node) || ts.isForOfStatement(node) || ts.isForInStatement(node) || ts.isWhileStatement(node)) body = node.statement;
|
|
194
|
+
else if (ts.isCallExpression(node) && (propAccessName(node) === "map" || propAccessName(node) === "forEach")) {
|
|
195
|
+
const cb = node.arguments[0];
|
|
196
|
+
if (cb && (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb))) body = cb.body;
|
|
197
|
+
}
|
|
198
|
+
if (!body) return;
|
|
199
|
+
const occ = /* @__PURE__ */ new Map();
|
|
200
|
+
const walk = (n) => {
|
|
201
|
+
if (n !== body && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))) return;
|
|
202
|
+
if (ts.isCallExpression(n) && n.arguments.length === 0 && ts.isIdentifier(n.expression) && accessors.has(n.expression.text)) {
|
|
203
|
+
const id = n.expression.text;
|
|
204
|
+
const e = occ.get(id);
|
|
205
|
+
if (e) e.count++;
|
|
206
|
+
else occ.set(id, {
|
|
207
|
+
count: 1,
|
|
208
|
+
first: n
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
ts.forEachChild(n, walk);
|
|
212
|
+
};
|
|
213
|
+
walk(body);
|
|
214
|
+
for (const [id, e] of occ) if (e.count >= 2) emit(e.first, "MDC_PERF_004", `\`${id}()\` is read ${e.count}× inside a loop — each call re-reads the signal. Hoist it once above the loop: const v = ${id}().`);
|
|
215
|
+
};
|
|
216
|
+
const ruleEffectNoCleanup = (node) => {
|
|
217
|
+
if (!ts.isCallExpression(node) || calleeName(node) !== "effect") return;
|
|
218
|
+
const fn = node.arguments[0];
|
|
219
|
+
if (!fn || !ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return;
|
|
220
|
+
let subscribes = false;
|
|
221
|
+
let hasCleanup = false;
|
|
222
|
+
const look = (n) => {
|
|
223
|
+
if (ts.isCallExpression(n)) {
|
|
224
|
+
const nm = ts.isIdentifier(n.expression) ? n.expression.text : propAccessName(n);
|
|
225
|
+
if (SUBSCRIBE_CALLS.test(nm)) subscribes = true;
|
|
226
|
+
if (ts.isIdentifier(n.expression) && n.expression.text === "onCleanup") hasCleanup = true;
|
|
227
|
+
}
|
|
228
|
+
if (ts.isReturnStatement(n) && n.expression && (ts.isArrowFunction(n.expression) || ts.isFunctionExpression(n.expression) || ts.isIdentifier(n.expression))) hasCleanup = true;
|
|
229
|
+
ts.forEachChild(n, look);
|
|
230
|
+
};
|
|
231
|
+
look(fn.body);
|
|
232
|
+
if (subscribes && !hasCleanup) emit(node, "MDC_PERF_005", "effect() subscribes to an external source but returns no cleanup — the listener leaks when the scope disposes. Return a teardown function or call onCleanup(() => …).");
|
|
233
|
+
};
|
|
234
|
+
const ruleConstFunctionStyle = (node) => {
|
|
235
|
+
if (!ts.isJsxAttribute(node) || !node.initializer) return;
|
|
236
|
+
if (!ts.isJsxExpression(node.initializer) || !node.initializer.expression) return;
|
|
237
|
+
const expr = node.initializer.expression;
|
|
238
|
+
if (!ts.isArrowFunction(expr) || ts.isBlock(expr.body)) return;
|
|
239
|
+
let body = expr.body;
|
|
240
|
+
while (ts.isParenthesizedExpression(body)) body = body.expression;
|
|
241
|
+
if (!ts.isObjectLiteralExpression(body) && !ts.isArrayLiteralExpression(body)) return;
|
|
242
|
+
if (!containsAnyCall(body)) emit(node, "MDC_PERF_006", "A function-valued prop that returns a constant object/array (no reads) allocates a reactive binding for a value that never changes. Pass the literal directly (style={{…}}).");
|
|
243
|
+
};
|
|
244
|
+
const ruleLargeLiteralList = (node) => {
|
|
245
|
+
if (ts.isJsxElement(node) || ts.isJsxFragment(node)) {
|
|
246
|
+
const count = node.children.filter((c) => ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c) || ts.isJsxFragment(c)).length;
|
|
247
|
+
if (count >= threshold) emit(node, "MDC_PERF_007", `${count} static child elements rendered inline — consider List({...}) virtualization for large lists.`);
|
|
248
|
+
} else if (ts.isArrayLiteralExpression(node)) {
|
|
249
|
+
const count = node.elements.filter((c) => ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c) || ts.isJsxFragment(c)).length;
|
|
250
|
+
if (count >= threshold) emit(node, "MDC_PERF_007", `${count} JSX elements in an array literal — consider For/List for large lists.`);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const visit = (node) => {
|
|
254
|
+
ruleMapJsxChild(node);
|
|
255
|
+
ruleMissingKey(node);
|
|
256
|
+
ruleHeavyEffect(node);
|
|
257
|
+
ruleRepeatedReadInLoop(node);
|
|
258
|
+
ruleEffectNoCleanup(node);
|
|
259
|
+
ruleConstFunctionStyle(node);
|
|
260
|
+
ruleLargeLiteralList(node);
|
|
261
|
+
ts.forEachChild(node, visit);
|
|
262
|
+
};
|
|
263
|
+
visit(sf);
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
export { perfLint };
|
|
268
|
+
|
|
269
|
+
//# sourceMappingURL=perf-lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf-lint.js","names":[],"sources":["../src/perf-lint.ts"],"sourcesContent":["/**\n * Build-time **perf-lint** — an opt-in pass that flags real performance footguns in the\n * fine-grained reactive + Helix render model, as `warning` {@link Diagnostic}s (it NEVER blocks the\n * build). Honest by design: every rule reports a concrete structural fact and *why* it's slow in\n * THIS model — no invented frame-time numbers. A diagnostic neither React Native nor Flutter ships.\n *\n * Enable via `compileChecked(src, { perf: true })` (or `{ perf: { rules, listSizeThreshold } }`).\n * Suppress a finding with a leading `// mdc-perf-ignore` (all) or `// mdc-perf-ignore MDC_PERF_001`\n * (one code) comment, or `rules: { MDC_PERF_001: 'off' }`.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport { scriptKindForFile } from './typecheck'\nimport type { Diagnostic } from './types'\n\n/** Per-call configuration for {@link perfLint}. */\nexport interface PerfLintOptions {\n /** Turn individual rules on/off (default: all on except `MDC_PERF_007`). */\n readonly rules?: Record<string, 'off' | 'warning'>\n /** Element count at/above which `MDC_PERF_007` fires (default 50). */\n readonly listSizeThreshold?: number\n /** Identifiers treated as keyed list builders (default For/keyedRegion/List/createList/SectionList/createSectionList). */\n readonly keyedNames?: readonly string[]\n}\n\nconst DEFAULT_KEYED = [\n 'For',\n 'keyedRegion',\n 'List',\n 'createList',\n 'SectionList',\n 'createSectionList',\n]\nconst OFF_BY_DEFAULT = new Set(['MDC_PERF_007'])\n/** A `const x = signal()|computed()|memo()|use<Capital>()` binds a reactive accessor. */\nconst ACCESSOR_FACTORY = /^(signal|computed|memo|use[A-Z])/\nconst HEAVY_METHODS = new Set([\n 'map',\n 'filter',\n 'reduce',\n 'forEach',\n 'sort',\n 'flatMap',\n 'reduceRight',\n])\nconst SUBSCRIBE_CALLS =\n /(addEventListener|setInterval|setTimeout|subscribe|^on$|observe|addListener)/\n\n/** Run the perf-lint over one module's source; returns `warning` diagnostics (possibly empty). */\nexport function perfLint(\n source: string,\n fileName = 'module.tsx',\n options: PerfLintOptions = {},\n): Diagnostic[] {\n const sf = ts.createSourceFile(\n fileName,\n source,\n ts.ScriptTarget.ES2023,\n /* setParentNodes */ true,\n scriptKindForFile(fileName),\n )\n const keyed = new Set(options.keyedNames ?? DEFAULT_KEYED)\n const threshold = options.listSizeThreshold ?? 50\n const out: Diagnostic[] = []\n\n const isEnabled = (code: string): boolean => {\n const setting = options.rules?.[code]\n if (setting) return setting !== 'off'\n return !OFF_BY_DEFAULT.has(code)\n }\n\n // --- pre-pass: the set of identifiers bound to reactive accessors (signal/computed/memo/use*) ---\n const accessors = new Set<string>()\n const collectAccessors = (node: ts.Node): void => {\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n ts.isCallExpression(node.initializer)\n ) {\n const callee = node.initializer.expression\n const name = ts.isIdentifier(callee)\n ? callee.text\n : ts.isPropertyAccessExpression(callee)\n ? callee.name.text\n : ''\n if (ACCESSOR_FACTORY.test(name)) accessors.add(node.name.text)\n }\n ts.forEachChild(node, collectAccessors)\n }\n collectAccessors(sf)\n\n const positionOf = (node: ts.Node) => {\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf))\n return { line: line + 1, column: character + 1 }\n }\n // Line-based suppression: an `mdc-perf-ignore [CODE]` comment on the finding's line or the line\n // above. This works uniformly for `//` comments AND JSX `{/* … */}` comment children (whose comment\n // text isn't in the flagged node's leading trivia).\n const sourceLines = source.split('\\n')\n const isSuppressed = (node: ts.Node, code: string): boolean => {\n const { line } = sf.getLineAndCharacterOfPosition(node.getStart(sf))\n for (const ln of [line, line - 1]) {\n const m = /mdc-perf-ignore(?:\\s+(MDC_PERF_\\d+))?/.exec(sourceLines[ln] ?? '')\n if (m && (!m[1] || m[1] === code)) return true\n }\n return false\n }\n const emit = (node: ts.Node, code: string, message: string): void => {\n if (!isEnabled(code) || isSuppressed(node, code)) return\n out.push({ severity: 'warning', code, message, file: fileName, position: positionOf(node) })\n }\n\n // --- shared AST helpers ---\n const unwrapArrow = (expr: ts.Expression): ts.Expression => {\n // `() => X` → X (the arrow's expression body), else the node itself.\n if (ts.isArrowFunction(expr) && !ts.isBlock(expr.body)) return expr.body\n return expr\n }\n const returnsJsx = (fn: ts.Expression): boolean => {\n if (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) return false\n let found = false\n const look = (n: ts.Node): void => {\n if (found) return\n if (ts.isJsxElement(n) || ts.isJsxFragment(n) || ts.isJsxSelfClosingElement(n)) {\n found = true\n return\n }\n // don't descend into a NESTED function (its JSX isn't this callback's return)\n if (\n n !== fn &&\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))\n )\n return\n ts.forEachChild(n, look)\n }\n look(fn.body)\n return found\n }\n const calleeName = (call: ts.CallExpression): string =>\n ts.isIdentifier(call.expression) ? call.expression.text : ''\n const propAccessName = (call: ts.CallExpression): string =>\n ts.isPropertyAccessExpression(call.expression) ? call.expression.name.text : ''\n /** Does the subtree contain ANY call expression? (A returned literal with no call is provably static.) */\n const containsAnyCall = (root: ts.Node): boolean => {\n let found = false\n const look = (n: ts.Node): void => {\n if (found) return\n if (ts.isCallExpression(n)) {\n found = true\n return\n }\n ts.forEachChild(n, look)\n }\n look(root)\n return found\n }\n /** Does the subtree reactively read a known accessor (a zero-arg call `acc()`)? */\n const readsAccessor = (root: ts.Node): boolean => {\n let found = false\n const look = (n: ts.Node): void => {\n if (found) return\n if (\n ts.isCallExpression(n) &&\n n.arguments.length === 0 &&\n ts.isIdentifier(n.expression) &&\n accessors.has(n.expression.text)\n ) {\n found = true\n return\n }\n ts.forEachChild(n, look)\n }\n look(root)\n return found\n }\n /** Does the subtree contain a heavy synchronous construct (loop / array-method chain / JSON)? */\n const hasHeavyWork = (root: ts.Node): boolean => {\n let found = false\n const look = (n: ts.Node): void => {\n if (found) return\n if (\n ts.isForStatement(n) ||\n ts.isForOfStatement(n) ||\n ts.isForInStatement(n) ||\n ts.isWhileStatement(n)\n ) {\n found = true\n return\n }\n if (ts.isCallExpression(n) && HEAVY_METHODS.has(propAccessName(n))) {\n found = true\n return\n }\n if (\n ts.isCallExpression(n) &&\n ts.isPropertyAccessExpression(n.expression) &&\n ts.isIdentifier(n.expression.expression) &&\n n.expression.expression.text === 'JSON'\n ) {\n found = true\n return\n }\n ts.forEachChild(n, look)\n }\n look(root)\n return found\n }\n\n // --- rule matchers ---\n const ruleMapJsxChild = (node: ts.Node): void => {\n if (!ts.isJsxExpression(node) || !node.expression) return\n const parent = node.parent\n if (!parent || !(ts.isJsxElement(parent) || ts.isJsxFragment(parent))) return\n const expr = unwrapArrow(node.expression)\n if (!ts.isCallExpression(expr) || propAccessName(expr) !== 'map') return\n const cb = expr.arguments[0]\n if (!cb || !returnsJsx(cb)) return\n emit(\n node,\n 'MDC_PERF_001',\n 'List rendered with a bare .map() — on any change this region re-mounts EVERY row ' +\n '(losing focus/scroll/state). Use For({ each, key, children }) (keyed: only the diff is ' +\n 'created/moved/disposed) or List({...}) for large lists.',\n )\n }\n\n const ruleMissingKey = (node: ts.Node): void => {\n if (!ts.isCallExpression(node)) return\n const name = calleeName(node)\n if (!keyed.has(name)) return\n const arg = node.arguments[0]\n if (!arg || !ts.isObjectLiteralExpression(arg)) return\n let hasKey = false\n let hasSpread = false\n let hasEach = false\n for (const p of arg.properties) {\n if (ts.isSpreadAssignment(p)) hasSpread = true\n if (\n (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&\n p.name &&\n ts.isIdentifier(p.name)\n ) {\n if (p.name.text === 'key') hasKey = true\n if (p.name.text === 'each') hasEach = true\n }\n }\n // Only the `{ each, children }` keyed-region shape (For/keyedRegion) defaults to identity keying;\n // List/createList key differently (e.g. keyExtractor) and use a different option shape — requiring\n // `each` avoids false-positives on those.\n if (hasKey || hasSpread || !hasEach) return\n emit(\n node,\n 'MDC_PERF_002',\n `${name}() has no \\`key\\` — it defaults to object identity, so freshly-built rows never ` +\n 'match and every row is disposed + recreated (or throws on a duplicate primitive). Add ' +\n 'key: (item) => item.id (a stable id).',\n )\n }\n\n const ruleHeavyEffect = (node: ts.Node): void => {\n if (!ts.isCallExpression(node) || calleeName(node) !== 'effect') return\n // effect(fn, { priority: 'normal' }) is already deferred — exempt.\n if (node.arguments.length >= 2) return\n const fn = node.arguments[0]\n if (!fn || (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))) return\n if (hasHeavyWork(fn.body) && readsAccessor(fn.body)) {\n emit(\n node,\n 'MDC_PERF_003',\n 'Heavy synchronous work in a default (sync-lane) effect — it runs inline on every ' +\n 'dependency write, blocking the interaction. Move derived values into computed()/memo() ' +\n \"(lazy + cached), or run heavy work on the deferred lane: effect(fn, { priority: 'normal' }).\",\n )\n }\n }\n\n const ruleRepeatedReadInLoop = (node: ts.Node): void => {\n let body: ts.Node | undefined\n if (\n ts.isForStatement(node) ||\n ts.isForOfStatement(node) ||\n ts.isForInStatement(node) ||\n ts.isWhileStatement(node)\n ) {\n body = node.statement\n } else if (\n ts.isCallExpression(node) &&\n (propAccessName(node) === 'map' || propAccessName(node) === 'forEach')\n ) {\n const cb = node.arguments[0]\n if (cb && (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb))) body = cb.body\n }\n if (!body) return\n // Count zero-arg reads of each known accessor inside the loop body. A nested function resets the\n // scope (its reads run later, not per-iteration), so don't descend into one.\n const occ = new Map<string, { count: number; first: ts.Node }>()\n const walk = (n: ts.Node): void => {\n if (\n n !== body &&\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))\n )\n return\n if (\n ts.isCallExpression(n) &&\n n.arguments.length === 0 &&\n ts.isIdentifier(n.expression) &&\n accessors.has(n.expression.text)\n ) {\n const id = n.expression.text\n const e = occ.get(id)\n if (e) e.count++\n else occ.set(id, { count: 1, first: n })\n }\n ts.forEachChild(n, walk)\n }\n walk(body)\n for (const [id, e] of occ) {\n if (e.count >= 2) {\n emit(\n e.first,\n 'MDC_PERF_004',\n `\\`${id}()\\` is read ${e.count}× inside a loop — each call re-reads the signal. Hoist ` +\n `it once above the loop: const v = ${id}().`,\n )\n }\n }\n }\n\n const ruleEffectNoCleanup = (node: ts.Node): void => {\n if (!ts.isCallExpression(node) || calleeName(node) !== 'effect') return\n const fn = node.arguments[0]\n if (!fn || (!ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn))) return\n let subscribes = false\n let hasCleanup = false\n const look = (n: ts.Node): void => {\n if (ts.isCallExpression(n)) {\n const nm = ts.isIdentifier(n.expression) ? n.expression.text : propAccessName(n)\n if (SUBSCRIBE_CALLS.test(nm)) subscribes = true\n if (ts.isIdentifier(n.expression) && n.expression.text === 'onCleanup') hasCleanup = true\n }\n // A `return <function>` OR `return <identifier>` is a teardown: effect() registers ANY returned\n // function value as cleanup (reactive.ts), so `const t = …; return t` / `return unsub` count.\n if (\n ts.isReturnStatement(n) &&\n n.expression &&\n (ts.isArrowFunction(n.expression) ||\n ts.isFunctionExpression(n.expression) ||\n ts.isIdentifier(n.expression))\n )\n hasCleanup = true\n ts.forEachChild(n, look)\n }\n look(fn.body)\n if (subscribes && !hasCleanup) {\n emit(\n node,\n 'MDC_PERF_005',\n 'effect() subscribes to an external source but returns no cleanup — the listener leaks ' +\n 'when the scope disposes. Return a teardown function or call onCleanup(() => …).',\n )\n }\n }\n\n const ruleConstFunctionStyle = (node: ts.Node): void => {\n if (!ts.isJsxAttribute(node) || !node.initializer) return\n if (!ts.isJsxExpression(node.initializer) || !node.initializer.expression) return\n const expr = node.initializer.expression\n if (!ts.isArrowFunction(expr) || ts.isBlock(expr.body)) return\n let body: ts.Expression = expr.body\n while (ts.isParenthesizedExpression(body)) body = body.expression // `() => ({...})` parens\n if (!ts.isObjectLiteralExpression(body) && !ts.isArrayLiteralExpression(body)) return\n // Only flag a PROVABLY-STATIC literal — one with NO call expression at all. A body that calls\n // anything (theme(), props.active(), select()/destructured/param accessors, helpers) may read a\n // live signal; flagging it and \"passing the literal directly\" would BREAK reactivity. Erring to\n // silence here keeps the rule from firing on the #1 styled-component pattern.\n if (!containsAnyCall(body)) {\n emit(\n node,\n 'MDC_PERF_006',\n 'A function-valued prop that returns a constant object/array (no reads) allocates a reactive ' +\n 'binding for a value that never changes. Pass the literal directly (style={{…}}).',\n )\n }\n }\n\n const ruleLargeLiteralList = (node: ts.Node): void => {\n if (ts.isJsxElement(node) || ts.isJsxFragment(node)) {\n const count = node.children.filter(\n (c) => ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c) || ts.isJsxFragment(c),\n ).length\n if (count >= threshold)\n emit(\n node,\n 'MDC_PERF_007',\n `${count} static child elements rendered inline — consider List({...}) virtualization for large lists.`,\n )\n } else if (ts.isArrayLiteralExpression(node)) {\n const count = node.elements.filter(\n (c) => ts.isJsxElement(c) || ts.isJsxSelfClosingElement(c) || ts.isJsxFragment(c),\n ).length\n if (count >= threshold)\n emit(\n node,\n 'MDC_PERF_007',\n `${count} JSX elements in an array literal — consider For/List for large lists.`,\n )\n }\n }\n\n const visit = (node: ts.Node): void => {\n ruleMapJsxChild(node)\n ruleMissingKey(node)\n ruleHeavyEffect(node)\n ruleRepeatedReadInLoop(node)\n ruleEffectNoCleanup(node)\n ruleConstFunctionStyle(node)\n ruleLargeLiteralList(node)\n ts.forEachChild(node, visit)\n }\n visit(sf)\n return out\n}\n"],"mappings":"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"}
|
package/dist/transform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","names":[],"sources":["../src/transform.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","names":[],"sources":["../src/transform.ts"],"mappings":";;;AA+FoF;AAuDpF;;;;;;;AAvDoF,iBAApE,OAAA,CAAQ,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa;;AAuDO;;;iBAA3E,cAAA,CAAe,MAAA,UAAgB,OAAA,GAAS,cAAA,GAAsB,aAAa"}
|
package/dist/transform.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFlattenTransformer } from "./flatten.js";
|
|
2
2
|
import { hasErrors, typecheck } from "./typecheck.js";
|
|
3
|
+
import { perfLint } from "./perf-lint.js";
|
|
3
4
|
import ts from "typescript";
|
|
4
5
|
//#region src/transform.ts
|
|
5
6
|
/**
|
|
@@ -102,7 +103,8 @@ function compile(source, options = {}) {
|
|
|
102
103
|
* WITHOUT emitting code (`code: ''`) — the build must not ship type errors.
|
|
103
104
|
*/
|
|
104
105
|
function compileChecked(source, options = {}) {
|
|
105
|
-
const
|
|
106
|
+
const fileName = options.fileName ?? "module.tsx";
|
|
107
|
+
const diagnostics = typecheck(source, fileName);
|
|
106
108
|
if (hasErrors(diagnostics)) return {
|
|
107
109
|
code: "",
|
|
108
110
|
diagnostics,
|
|
@@ -112,9 +114,14 @@ function compileChecked(source, options = {}) {
|
|
|
112
114
|
}
|
|
113
115
|
};
|
|
114
116
|
const compiled = compile(source, options);
|
|
117
|
+
const perfDiagnostics = options.perf ? perfLint(source, fileName, typeof options.perf === "object" ? options.perf : {}) : [];
|
|
115
118
|
return {
|
|
116
119
|
...compiled,
|
|
117
|
-
diagnostics: [
|
|
120
|
+
diagnostics: [
|
|
121
|
+
...diagnostics,
|
|
122
|
+
...perfDiagnostics,
|
|
123
|
+
...compiled.diagnostics
|
|
124
|
+
]
|
|
118
125
|
};
|
|
119
126
|
}
|
|
120
127
|
//#endregion
|
package/dist/transform.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.js","names":[],"sources":["../src/transform.ts"],"sourcesContent":["/**\n * The MDC transform/compile pipeline.\n *\n * `compile()` lowers TSX → `createElement(...)` (matching `@mindees/core`'s\n * factory), runs the built-in optimizer passes (tree-flattening) plus any user\n * plugins, and emits JavaScript + a source map. It does **not** type-check\n * (that's {@link typecheck}); `compileChecked()` runs the gate first and refuses\n * to emit on `error` diagnostics.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport { createFlattenTransformer } from './flatten'\nimport { hasErrors, typecheck } from './typecheck'\nimport type { CompileOptions, CompileResult, CompileStats } from './types'\n\n/** Compiler options for emit (JSX → `createElement`/`Fragment`, which the optimizer matches). */\nfunction emitOptions(sourceMap: boolean): ts.CompilerOptions {\n return {\n jsx: ts.JsxEmit.React,\n jsxFactory: 'createElement',\n jsxFragmentFactory: 'Fragment',\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n sourceMap,\n }\n}\n\n/** Runtime names the JSX desugar references; injected from `@mindees/core` if unbound. */\nconst RUNTIME_NAMES = ['createElement', 'Fragment'] as const\n\n/**\n * Ensure the JSX runtime is in scope. Idiomatic components use **automatic JSX** and import\n * nothing, but we emit classic `createElement(...)`/`Fragment` (so the tree-flatten optimizer\n * can match them) — which would be unbound at runtime. This transformer prepends\n * `import { createElement, Fragment } from '@mindees/core'` for any runtime name that is\n * referenced but not already imported, so emitted modules run. Runs LAST (after flatten/plugins),\n * so names the optimizer removed don't get a needless import.\n */\nfunction createRuntimeImportTransformer(tsmod: typeof ts): ts.TransformerFactory<ts.SourceFile> {\n return (context) => (sourceFile) => {\n const imported = new Set<string>()\n for (const stmt of sourceFile.statements) {\n if (\n tsmod.isImportDeclaration(stmt) &&\n tsmod.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@mindees/core'\n ) {\n const named = stmt.importClause?.namedBindings\n if (named && tsmod.isNamedImports(named)) {\n for (const el of named.elements) imported.add((el.propertyName ?? el.name).text)\n }\n }\n }\n const referenced = new Set<string>()\n const visit = (node: ts.Node): void => {\n if (tsmod.isIdentifier(node) && (RUNTIME_NAMES as readonly string[]).includes(node.text)) {\n referenced.add(node.text)\n }\n tsmod.forEachChild(node, visit)\n }\n visit(sourceFile)\n const missing = RUNTIME_NAMES.filter((n) => referenced.has(n) && !imported.has(n))\n if (missing.length === 0) return sourceFile\n const importDecl = tsmod.factory.createImportDeclaration(\n undefined,\n tsmod.factory.createImportClause(\n false,\n undefined,\n tsmod.factory.createNamedImports(\n missing.map((n) =>\n tsmod.factory.createImportSpecifier(\n false,\n undefined,\n tsmod.factory.createIdentifier(n),\n ),\n ),\n ),\n ),\n tsmod.factory.createStringLiteral('@mindees/core'),\n )\n return context.factory.updateSourceFile(sourceFile, [importDecl, ...sourceFile.statements])\n }\n}\n\n/**\n * Compile a single TSX/TS module to JavaScript.\n *\n * Pipeline: JSX desugar → tree-flatten (optional) → user plugins → emit.\n * Returns emitted code, an optional source map, any (transpile-level)\n * diagnostics, and optimizer stats. Use {@link compileChecked} to gate on the\n * full type checker.\n */\nexport function compile(source: string, options: CompileOptions = {}): CompileResult {\n const { fileName = 'module.tsx', sourceMap = true, flatten = true, plugins = [] } = options\n\n // IMPORTANT: our optimizer + plugins operate on the desugared\n // `createElement(...)` call form, but `transpileModule` runs `before`\n // transformers on the *pre-desugar* JSX AST. JSX is lowered during the\n // `after` phase, so flatten/plugins must run there to see the calls.\n const after: ts.TransformerFactory<ts.SourceFile>[] = []\n let stats: CompileStats = { flattenedNodes: 0, totalElements: 0 }\n\n if (flatten) {\n const flattener = createFlattenTransformer(ts)\n after.push(flattener.factory)\n stats = flattener.stats // live object, updated during emit\n }\n\n for (const plugin of plugins) {\n after.push(plugin.transformer(ts) as ts.TransformerFactory<ts.SourceFile>)\n }\n\n // LAST: bind the JSX runtime (automatic-JSX components import nothing) so output runs.\n after.push(createRuntimeImportTransformer(ts))\n\n const output = ts.transpileModule(source, {\n compilerOptions: emitOptions(sourceMap),\n fileName,\n reportDiagnostics: true,\n transformers: { after },\n })\n\n // transpileModule only surfaces a few syntactic diagnostics; semantic ones\n // come from the type-check gate. Map each to our structured form.\n const diagnostics = (output.diagnostics ?? []).map((d) => {\n const message = ts.flattenDiagnosticMessageText(d.messageText, '\\n')\n return {\n severity:\n d.category === ts.DiagnosticCategory.Error ? ('error' as const) : ('warning' as const),\n code: `TS${d.code}`,\n message,\n }\n })\n\n const result: CompileResult = {\n code: output.outputText,\n diagnostics,\n stats,\n }\n if (sourceMap && output.sourceMapText) result.map = output.sourceMapText\n return result\n}\n\n/**\n * Type-check then compile. If the gate finds any `error` diagnostic, returns it\n * WITHOUT emitting code (`code: ''`) — the build must not ship type errors.\n */\nexport function compileChecked(source: string, options: CompileOptions = {}): CompileResult {\n const fileName = options.fileName ?? 'module.tsx'\n const diagnostics = typecheck(source, fileName)\n if (hasErrors(diagnostics)) {\n return { code: '', diagnostics, stats: { flattenedNodes: 0, totalElements: 0 } }\n }\n const compiled = compile(source, options)\n // Surface
|
|
1
|
+
{"version":3,"file":"transform.js","names":[],"sources":["../src/transform.ts"],"sourcesContent":["/**\n * The MDC transform/compile pipeline.\n *\n * `compile()` lowers TSX → `createElement(...)` (matching `@mindees/core`'s\n * factory), runs the built-in optimizer passes (tree-flattening) plus any user\n * plugins, and emits JavaScript + a source map. It does **not** type-check\n * (that's {@link typecheck}); `compileChecked()` runs the gate first and refuses\n * to emit on `error` diagnostics.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport { createFlattenTransformer } from './flatten'\nimport { perfLint } from './perf-lint'\nimport { hasErrors, typecheck } from './typecheck'\nimport type { CompileOptions, CompileResult, CompileStats } from './types'\n\n/** Compiler options for emit (JSX → `createElement`/`Fragment`, which the optimizer matches). */\nfunction emitOptions(sourceMap: boolean): ts.CompilerOptions {\n return {\n jsx: ts.JsxEmit.React,\n jsxFactory: 'createElement',\n jsxFragmentFactory: 'Fragment',\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n sourceMap,\n }\n}\n\n/** Runtime names the JSX desugar references; injected from `@mindees/core` if unbound. */\nconst RUNTIME_NAMES = ['createElement', 'Fragment'] as const\n\n/**\n * Ensure the JSX runtime is in scope. Idiomatic components use **automatic JSX** and import\n * nothing, but we emit classic `createElement(...)`/`Fragment` (so the tree-flatten optimizer\n * can match them) — which would be unbound at runtime. This transformer prepends\n * `import { createElement, Fragment } from '@mindees/core'` for any runtime name that is\n * referenced but not already imported, so emitted modules run. Runs LAST (after flatten/plugins),\n * so names the optimizer removed don't get a needless import.\n */\nfunction createRuntimeImportTransformer(tsmod: typeof ts): ts.TransformerFactory<ts.SourceFile> {\n return (context) => (sourceFile) => {\n const imported = new Set<string>()\n for (const stmt of sourceFile.statements) {\n if (\n tsmod.isImportDeclaration(stmt) &&\n tsmod.isStringLiteral(stmt.moduleSpecifier) &&\n stmt.moduleSpecifier.text === '@mindees/core'\n ) {\n const named = stmt.importClause?.namedBindings\n if (named && tsmod.isNamedImports(named)) {\n for (const el of named.elements) imported.add((el.propertyName ?? el.name).text)\n }\n }\n }\n const referenced = new Set<string>()\n const visit = (node: ts.Node): void => {\n if (tsmod.isIdentifier(node) && (RUNTIME_NAMES as readonly string[]).includes(node.text)) {\n referenced.add(node.text)\n }\n tsmod.forEachChild(node, visit)\n }\n visit(sourceFile)\n const missing = RUNTIME_NAMES.filter((n) => referenced.has(n) && !imported.has(n))\n if (missing.length === 0) return sourceFile\n const importDecl = tsmod.factory.createImportDeclaration(\n undefined,\n tsmod.factory.createImportClause(\n false,\n undefined,\n tsmod.factory.createNamedImports(\n missing.map((n) =>\n tsmod.factory.createImportSpecifier(\n false,\n undefined,\n tsmod.factory.createIdentifier(n),\n ),\n ),\n ),\n ),\n tsmod.factory.createStringLiteral('@mindees/core'),\n )\n return context.factory.updateSourceFile(sourceFile, [importDecl, ...sourceFile.statements])\n }\n}\n\n/**\n * Compile a single TSX/TS module to JavaScript.\n *\n * Pipeline: JSX desugar → tree-flatten (optional) → user plugins → emit.\n * Returns emitted code, an optional source map, any (transpile-level)\n * diagnostics, and optimizer stats. Use {@link compileChecked} to gate on the\n * full type checker.\n */\nexport function compile(source: string, options: CompileOptions = {}): CompileResult {\n const { fileName = 'module.tsx', sourceMap = true, flatten = true, plugins = [] } = options\n\n // IMPORTANT: our optimizer + plugins operate on the desugared\n // `createElement(...)` call form, but `transpileModule` runs `before`\n // transformers on the *pre-desugar* JSX AST. JSX is lowered during the\n // `after` phase, so flatten/plugins must run there to see the calls.\n const after: ts.TransformerFactory<ts.SourceFile>[] = []\n let stats: CompileStats = { flattenedNodes: 0, totalElements: 0 }\n\n if (flatten) {\n const flattener = createFlattenTransformer(ts)\n after.push(flattener.factory)\n stats = flattener.stats // live object, updated during emit\n }\n\n for (const plugin of plugins) {\n after.push(plugin.transformer(ts) as ts.TransformerFactory<ts.SourceFile>)\n }\n\n // LAST: bind the JSX runtime (automatic-JSX components import nothing) so output runs.\n after.push(createRuntimeImportTransformer(ts))\n\n const output = ts.transpileModule(source, {\n compilerOptions: emitOptions(sourceMap),\n fileName,\n reportDiagnostics: true,\n transformers: { after },\n })\n\n // transpileModule only surfaces a few syntactic diagnostics; semantic ones\n // come from the type-check gate. Map each to our structured form.\n const diagnostics = (output.diagnostics ?? []).map((d) => {\n const message = ts.flattenDiagnosticMessageText(d.messageText, '\\n')\n return {\n severity:\n d.category === ts.DiagnosticCategory.Error ? ('error' as const) : ('warning' as const),\n code: `TS${d.code}`,\n message,\n }\n })\n\n const result: CompileResult = {\n code: output.outputText,\n diagnostics,\n stats,\n }\n if (sourceMap && output.sourceMapText) result.map = output.sourceMapText\n return result\n}\n\n/**\n * Type-check then compile. If the gate finds any `error` diagnostic, returns it\n * WITHOUT emitting code (`code: ''`) — the build must not ship type errors.\n */\nexport function compileChecked(source: string, options: CompileOptions = {}): CompileResult {\n const fileName = options.fileName ?? 'module.tsx'\n const diagnostics = typecheck(source, fileName)\n if (hasErrors(diagnostics)) {\n return { code: '', diagnostics, stats: { flattenedNodes: 0, totalElements: 0 } }\n }\n const compiled = compile(source, options)\n // Opt-in build-time perf-lint: warnings only (never blocks — the gate above already returned on\n // errors, and every perf diagnostic is severity 'warning').\n const perfDiagnostics = options.perf\n ? perfLint(source, fileName, typeof options.perf === 'object' ? options.perf : {})\n : []\n // Surface type-check warnings + perf warnings alongside the compile result.\n return {\n ...compiled,\n diagnostics: [...diagnostics, ...perfDiagnostics, ...compiled.diagnostics],\n }\n}\n"],"mappings":"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"}
|
package/dist/typecheck.d.ts
CHANGED
package/dist/typecheck.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typecheck.d.ts","names":[],"sources":["../src/typecheck.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"typecheck.d.ts","names":[],"sources":["../src/typecheck.ts"],"mappings":";;;;;;;;;;;iBAsIgB,SAAA,CAAU,MAAA,UAAgB,QAAA,YAA0B,UAAU;;iBAuD9D,SAAA,CAAU,WAAkC,WAAZ,UAAU"}
|
package/dist/typecheck.js
CHANGED
package/dist/typecheck.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typecheck.js","names":[],"sources":["../src/typecheck.ts"],"sourcesContent":["/**\n * The MDC type-check gate.\n *\n * Runs the TypeScript checker over a single in-memory module and reports\n * structured {@link Diagnostic}s. This is the compiler's correctness gate: a\n * build with any `error`-severity diagnostic must not ship.\n *\n * It uses an in-memory `CompilerHost` so no files touch disk; lib `.d.ts` files\n * are read from the real `typescript` install so global types resolve.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport type { Diagnostic } from './types'\n\n/**\n * Synthetic ambient JSX environment for the single-module gate. The framework ships\n * **automatic JSX** (`jsxImportSource: '@mindees/core'`), so the gate must type-check\n * idiomatic components that import NOTHING — JSX resolves through the runtime module's\n * `JSX` namespace. We declare that runtime module ambiently (production + dev) so the gate\n * finds it under `noResolve`, and keep a global `JSX` namespace as a backstop.\n */\nconst JSX_LIB_FILE = '__mindees_jsx__.d.ts'\nconst JSX_LIB_SOURCE = `declare namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n}\ndeclare module '@mindees/core/jsx-runtime' {\n export namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n }\n export const jsx: (...args: unknown[]) => unknown\n export const jsxs: (...args: unknown[]) => unknown\n export const Fragment: unknown\n}\ndeclare module '@mindees/core/jsx-dev-runtime' {\n export namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n }\n export const jsxDEV: (...args: unknown[]) => unknown\n export const Fragment: unknown\n}\n`\n\n/**\n * Module-resolution diagnostic codes the single-module gate cannot meaningfully\n * judge (it type-checks ONE module with `noResolve`, so every import is\n * \"unresolved\"). Cross-module resolution is the project-graph type-check's job.\n * - TS2307: Cannot find module '…'.\n * - TS2792: Cannot find module … (did you mean to set 'moduleResolution'?).\n */\nconst UNRESOLVED_IMPORT_CODES = new Set([2307, 2792])\n\n/** Default compiler options for the gate: strict, modern, JSX-aware. */\nfunction defaultOptions(): ts.CompilerOptions {\n return {\n strict: true,\n noUncheckedIndexedAccess: true,\n exactOptionalPropertyTypes: true,\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n // Automatic JSX — the framework's shipped runtime. Idiomatic components import\n // nothing; JSX resolves through `@mindees/core/jsx-runtime`'s `JSX` namespace\n // (declared ambiently in JSX_LIB_SOURCE so the gate finds it under noResolve).\n jsx: ts.JsxEmit.ReactJSX,\n jsxImportSource: '@mindees/core',\n noEmit: true,\n skipLibCheck: true,\n // Type-check a single module in isolation; imports are not resolved here\n // (unresolved-import diagnostics are filtered — see UNRESOLVED_IMPORT_CODES).\n noResolve: true,\n types: [],\n }\n}\n\nfunction toSeverity(category: ts.DiagnosticCategory): 'error' | 'warning' | null {\n if (category === ts.DiagnosticCategory.Error) return 'error'\n if (category === ts.DiagnosticCategory.Warning) return 'warning'\n return null\n}\n\n/**\n * Map a file extension to its `ScriptKind`. `ts.createSourceFile` does NOT infer\n * this — omitting it would mis-parse files — so we map it explicitly (not via\n * any TypeScript-internal helper). A `.ts` file thus rejects JSX, while `.tsx`\n * accepts it.\n */\
|
|
1
|
+
{"version":3,"file":"typecheck.js","names":[],"sources":["../src/typecheck.ts"],"sourcesContent":["/**\n * The MDC type-check gate.\n *\n * Runs the TypeScript checker over a single in-memory module and reports\n * structured {@link Diagnostic}s. This is the compiler's correctness gate: a\n * build with any `error`-severity diagnostic must not ship.\n *\n * It uses an in-memory `CompilerHost` so no files touch disk; lib `.d.ts` files\n * are read from the real `typescript` install so global types resolve.\n *\n * @module\n */\n\nimport ts from 'typescript'\nimport type { Diagnostic } from './types'\n\n/**\n * Synthetic ambient JSX environment for the single-module gate. The framework ships\n * **automatic JSX** (`jsxImportSource: '@mindees/core'`), so the gate must type-check\n * idiomatic components that import NOTHING — JSX resolves through the runtime module's\n * `JSX` namespace. We declare that runtime module ambiently (production + dev) so the gate\n * finds it under `noResolve`, and keep a global `JSX` namespace as a backstop.\n */\nconst JSX_LIB_FILE = '__mindees_jsx__.d.ts'\nconst JSX_LIB_SOURCE = `declare namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n}\ndeclare module '@mindees/core/jsx-runtime' {\n export namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n }\n export const jsx: (...args: unknown[]) => unknown\n export const jsxs: (...args: unknown[]) => unknown\n export const Fragment: unknown\n}\ndeclare module '@mindees/core/jsx-dev-runtime' {\n export namespace JSX {\n interface IntrinsicElements { [name: string]: Record<string, unknown> }\n type Element = unknown\n interface ElementChildrenAttribute { children: object }\n }\n export const jsxDEV: (...args: unknown[]) => unknown\n export const Fragment: unknown\n}\n`\n\n/**\n * Module-resolution diagnostic codes the single-module gate cannot meaningfully\n * judge (it type-checks ONE module with `noResolve`, so every import is\n * \"unresolved\"). Cross-module resolution is the project-graph type-check's job.\n * - TS2307: Cannot find module '…'.\n * - TS2792: Cannot find module … (did you mean to set 'moduleResolution'?).\n */\nconst UNRESOLVED_IMPORT_CODES = new Set([2307, 2792])\n\n/** Default compiler options for the gate: strict, modern, JSX-aware. */\nfunction defaultOptions(): ts.CompilerOptions {\n return {\n strict: true,\n noUncheckedIndexedAccess: true,\n exactOptionalPropertyTypes: true,\n target: ts.ScriptTarget.ES2023,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n // Automatic JSX — the framework's shipped runtime. Idiomatic components import\n // nothing; JSX resolves through `@mindees/core/jsx-runtime`'s `JSX` namespace\n // (declared ambiently in JSX_LIB_SOURCE so the gate finds it under noResolve).\n jsx: ts.JsxEmit.ReactJSX,\n jsxImportSource: '@mindees/core',\n noEmit: true,\n skipLibCheck: true,\n // Type-check a single module in isolation; imports are not resolved here\n // (unresolved-import diagnostics are filtered — see UNRESOLVED_IMPORT_CODES).\n noResolve: true,\n types: [],\n }\n}\n\nfunction toSeverity(category: ts.DiagnosticCategory): 'error' | 'warning' | null {\n if (category === ts.DiagnosticCategory.Error) return 'error'\n if (category === ts.DiagnosticCategory.Warning) return 'warning'\n return null\n}\n\n/**\n * Map a file extension to its `ScriptKind`. `ts.createSourceFile` does NOT infer\n * this — omitting it would mis-parse files — so we map it explicitly (not via\n * any TypeScript-internal helper). A `.ts` file thus rejects JSX, while `.tsx`\n * accepts it.\n */\nexport function scriptKindForFile(fileName: string): ts.ScriptKind {\n const lower = fileName.toLowerCase()\n if (lower.endsWith('.tsx')) return ts.ScriptKind.TSX\n if (lower.endsWith('.jsx')) return ts.ScriptKind.JSX\n if (lower.endsWith('.js') || lower.endsWith('.mjs') || lower.endsWith('.cjs')) {\n return ts.ScriptKind.JS\n }\n if (lower.endsWith('.json')) return ts.ScriptKind.JSON\n return ts.ScriptKind.TS\n}\n\n/** Convert a TypeScript diagnostic to our structured form. */\nfunction convert(diag: ts.Diagnostic): Diagnostic | null {\n // The single-module gate doesn't resolve imports; drop unresolved-import noise.\n if (UNRESOLVED_IMPORT_CODES.has(diag.code)) return null\n const severity = toSeverity(diag.category)\n if (!severity) return null\n const message = ts.flattenDiagnosticMessageText(diag.messageText, '\\n')\n const out: Diagnostic = {\n severity,\n code: `TS${diag.code}`,\n message,\n }\n if (diag.file) {\n out.file = diag.file.fileName\n if (diag.start !== undefined) {\n const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start)\n out.position = { line: line + 1, column: character + 1 }\n }\n }\n return out\n}\n\n/**\n * Type-check a single module's source and return its diagnostics.\n *\n * @param source - The module source (`.tsx` is assumed unless `fileName` says otherwise).\n * @param fileName - Logical file name. Default `\"module.tsx\"`.\n * @returns All `error`/`warning` diagnostics (semantic + syntactic). Empty = clean.\n */\nexport function typecheck(source: string, fileName = 'module.tsx'): Diagnostic[] {\n const options = defaultOptions()\n const sourceFile = ts.createSourceFile(\n fileName,\n source,\n ts.ScriptTarget.ES2023,\n true,\n scriptKindForFile(fileName),\n )\n // A synthetic ambient JSX lib so intrinsic elements (`<view>`, `<text>`, …)\n // type-check without each module declaring JSX.IntrinsicElements.\n const jsxLib = ts.createSourceFile(\n JSX_LIB_FILE,\n JSX_LIB_SOURCE,\n ts.ScriptTarget.ES2023,\n true,\n ts.ScriptKind.TS,\n )\n\n const defaultHost = ts.createCompilerHost(options)\n const host: ts.CompilerHost = {\n ...defaultHost,\n getSourceFile: (name, languageVersion, onError, shouldCreate) => {\n if (name === fileName) return sourceFile\n if (name === JSX_LIB_FILE) return jsxLib\n return defaultHost.getSourceFile(name, languageVersion, onError, shouldCreate)\n },\n writeFile: () => {\n /* noEmit */\n },\n fileExists: (name) =>\n name === fileName || name === JSX_LIB_FILE || defaultHost.fileExists(name),\n readFile: (name) =>\n name === fileName\n ? source\n : name === JSX_LIB_FILE\n ? JSX_LIB_SOURCE\n : defaultHost.readFile(name),\n }\n\n const program = ts.createProgram([JSX_LIB_FILE, fileName], options, host)\n const diagnostics = [\n ...program.getSyntacticDiagnostics(sourceFile),\n ...program.getSemanticDiagnostics(sourceFile),\n ]\n\n const out: Diagnostic[] = []\n for (const d of diagnostics) {\n const converted = convert(d)\n if (converted) out.push(converted)\n }\n return out\n}\n\n/** True if any diagnostic is an `error` (i.e. the build must fail). */\nexport function hasErrors(diagnostics: readonly Diagnostic[]): boolean {\n return diagnostics.some((d) => d.severity === 'error')\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
+
import { PerfLintOptions } from "./perf-lint.js";
|
|
2
|
+
|
|
1
3
|
//#region src/types.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Shared types for the Mindees Compiler (MDC).
|
|
4
|
-
*
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
4
|
/** Severity of a {@link Diagnostic}. */
|
|
8
5
|
type DiagnosticSeverity = 'error' | 'warning';
|
|
9
6
|
/** A 1-based source position. */
|
|
@@ -53,6 +50,11 @@ interface CompileOptions {
|
|
|
53
50
|
flatten?: boolean;
|
|
54
51
|
/** Additional transform plugins to run (after the built-in passes). */
|
|
55
52
|
plugins?: MdcPlugin[];
|
|
53
|
+
/**
|
|
54
|
+
* Run the build-time perf-lint (`compileChecked` only) — emits `warning` diagnostics for reactive/
|
|
55
|
+
* render footguns; never blocks the build. `true` for defaults, or pass {@link PerfLintOptions}.
|
|
56
|
+
*/
|
|
57
|
+
perf?: boolean | PerfLintOptions;
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* A transform plugin. Plugins operate on the desugared `createElement(...)` call
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;KASY,kBAAA;AAGZ;AAAA,UAAiB,cAAA;;EAEf,IAAA;EAEM;EAAN,MAAM;AAAA;;UAIS,UAAA;EACf,QAAA,EAAU,kBAAA;EAAA;EAEV,IAAA;EAEA;EAAA,OAAA;EAIA;EAFA,IAAA;EAEyB;EAAzB,QAAA,GAAW,cAAc;AAAA;;UAIV,aAAA;EAQI;EANnB,IAAA;EAEA;EAAA,GAAA;EAEa;EAAb,WAAA,EAAa,UAAA;EAEN;EAAP,KAAA,EAAO,YAAY;AAAA;AAIrB;AAAA,UAAiB,YAAA;;EAEf,cAAA;EAEa;EAAb,aAAa;AAAA;;UAIE,cAAA;EAEf;EAAA,QAAA;EAIA;EAFA,SAAA;EAIU;EAFV,OAAA;EAOiB;EALjB,OAAA,GAAU,SAAA;EAKsB;AAYlC;;;EAZE,IAAA,aAAiB,eAAe;AAAA;;;;AAoBa;;;;;;UAR9B,SAAA;;EAEf,IAAA;;;;;;EAMA,WAAA,GAAc,EAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindees/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "MindeesNative Compiler (MDC) — build-time optimizer: type-check gate, TSX→createElement transform, tree-flattening, per-route code-splitting, and a plugin API. TS→native AOT is a research track.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"typescript": "6.0.3",
|
|
27
|
-
"@mindees/core": "0.
|
|
27
|
+
"@mindees/core": "0.7.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsdown",
|