runtypex 0.1.8 → 0.1.9
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.
|
@@ -51,24 +51,39 @@ export default function tsTransformer(options) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
function _resolveTypeByName(program, sf, checker, name) {
|
|
54
|
+
// 1️⃣ <string|number|boolean|null|undefined>
|
|
55
|
+
const primitiveNames = ["string", "number", "boolean", "bigint", "symbol", "null", "undefined"];
|
|
56
|
+
if (primitiveNames.includes(name)) {
|
|
57
|
+
const map = {
|
|
58
|
+
string: checker.getStringType(),
|
|
59
|
+
number: checker.getNumberType(),
|
|
60
|
+
boolean: checker.getBooleanType(),
|
|
61
|
+
bigint: checker.getBigIntType(),
|
|
62
|
+
symbol: checker.getESSymbolType(),
|
|
63
|
+
null: checker.getNullType(),
|
|
64
|
+
undefined: checker.getUndefinedType(),
|
|
65
|
+
};
|
|
66
|
+
return map[name];
|
|
67
|
+
}
|
|
68
|
+
// 2️⃣ <Type|Interface|Enum> declared in the same file or other files
|
|
54
69
|
for (const file of program.getSourceFiles()) {
|
|
55
70
|
const decl = _findLocalDeclaration(file, name);
|
|
56
71
|
if (!decl)
|
|
57
72
|
continue;
|
|
58
73
|
if (ts.isInterfaceDeclaration(decl) || ts.isClassDeclaration(decl) || ts.isEnumDeclaration(decl)) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return checker.getDeclaredTypeOfSymbol(sym);
|
|
74
|
+
const symbol = decl.name ? checker.getSymbolAtLocation(decl.name) : null;
|
|
75
|
+
if (symbol)
|
|
76
|
+
return checker.getDeclaredTypeOfSymbol(symbol);
|
|
63
77
|
}
|
|
64
78
|
if (ts.isTypeAliasDeclaration(decl)) {
|
|
65
79
|
return checker.getTypeFromTypeNode(decl.type);
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
|
-
|
|
82
|
+
// 3️⃣ <Type|Interface|Enum> imported from other modules
|
|
83
|
+
const symbol = checker
|
|
69
84
|
.getSymbolsInScope(sf, ts.SymbolFlags.Type | ts.SymbolFlags.Alias | ts.SymbolFlags.Interface)
|
|
70
85
|
.find((s) => s.name === name);
|
|
71
|
-
return
|
|
86
|
+
return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
72
87
|
}
|
|
73
88
|
function _findLocalDeclaration(sf, name) {
|
|
74
89
|
let found;
|
|
@@ -31,9 +31,9 @@ export default function vitePluginRuntypex(options) {
|
|
|
31
31
|
if (!sf)
|
|
32
32
|
return;
|
|
33
33
|
let mutated = code;
|
|
34
|
-
//
|
|
34
|
+
// ① makeAssert<T>()
|
|
35
35
|
mutated = mutated.replace(/makeAssert<\s*([^>]+)\s*>\s*\(\s*\)/g, (_m, typeName) => _emitMakeAssert({ program, checker, sf, typeName, prod, removeInProd }) ?? _m);
|
|
36
|
-
//
|
|
36
|
+
// ② makeValidate<T>()
|
|
37
37
|
mutated = mutated.replace(/makeValidate<\s*([^>]+)\s*>\s*\(\s*\)/g, (_m, typeName) => _emitMakeValidate({ program, checker, sf, typeName, prod, removeInProd }) ?? _m);
|
|
38
38
|
return mutated === code ? null : { code: mutated, map: null };
|
|
39
39
|
},
|
|
@@ -45,9 +45,8 @@ export default function vitePluginRuntypex(options) {
|
|
|
45
45
|
function _createProgramFor(file) {
|
|
46
46
|
const tsconfig = _findNearestTsconfig(path.dirname(file));
|
|
47
47
|
const cfg = ts.readConfigFile(tsconfig, ts.sys.readFile);
|
|
48
|
-
if (cfg.error)
|
|
48
|
+
if (cfg.error)
|
|
49
49
|
throw new Error(ts.flattenDiagnosticMessageText(cfg.error.messageText, "\n"));
|
|
50
|
-
}
|
|
51
50
|
const parsed = ts.parseJsonConfigFileContent(cfg.config, ts.sys, path.dirname(tsconfig));
|
|
52
51
|
const program = ts.createProgram({ rootNames: parsed.fileNames, options: parsed.options });
|
|
53
52
|
const checker = program.getTypeChecker();
|
|
@@ -69,6 +68,9 @@ function _findNearestTsconfig(start) {
|
|
|
69
68
|
return fallback;
|
|
70
69
|
throw new Error("tsconfig.json not found");
|
|
71
70
|
}
|
|
71
|
+
// ──────────────────────────────────────────────
|
|
72
|
+
// ② Emit Helpers
|
|
73
|
+
// ──────────────────────────────────────────────
|
|
72
74
|
function _emitMakeValidate({ program, checker, sf, typeName, prod, removeInProd, }) {
|
|
73
75
|
if (removeInProd && prod)
|
|
74
76
|
return `((_)=>true)`;
|
|
@@ -87,32 +89,51 @@ function _emitMakeAssert({ program, checker, sf, typeName, prod, removeInProd, }
|
|
|
87
89
|
return `(function(){const G=${guard};return(i)=>{if(!G(i))throw new TypeError("[runtypex] Validation failed.");};})()`;
|
|
88
90
|
}
|
|
89
91
|
// ──────────────────────────────────────────────
|
|
90
|
-
// ③ Type Resolution (support interface/type/enum)
|
|
92
|
+
// ③ Type Resolution (support primitive/interface/type/enum)
|
|
91
93
|
// ──────────────────────────────────────────────
|
|
92
94
|
function _resolveTypeByName(program, sf, checker, name) {
|
|
93
|
-
//
|
|
95
|
+
// -1️⃣ Primitive type fallback
|
|
96
|
+
const primitiveNames = ["string", "number", "boolean", "bigint", "symbol", "null", "undefined"];
|
|
97
|
+
if (primitiveNames.includes(name)) {
|
|
98
|
+
const map = {
|
|
99
|
+
string: checker.getStringType(),
|
|
100
|
+
number: checker.getNumberType(),
|
|
101
|
+
boolean: checker.getBooleanType(),
|
|
102
|
+
bigint: checker.getBigIntType(),
|
|
103
|
+
symbol: checker.getESSymbolType(),
|
|
104
|
+
null: checker.getNullType(),
|
|
105
|
+
undefined: checker.getUndefinedType(),
|
|
106
|
+
};
|
|
107
|
+
return map[name];
|
|
108
|
+
}
|
|
109
|
+
// 2️⃣ Scan source files
|
|
94
110
|
for (const file of program.getSourceFiles()) {
|
|
95
111
|
const decl = _findLocalDeclaration(file, name);
|
|
96
112
|
if (!decl)
|
|
97
113
|
continue;
|
|
98
|
-
// interface
|
|
99
|
-
if (ts.isInterfaceDeclaration(decl) ||
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
114
|
+
// ✅ type, interface, enum, class
|
|
115
|
+
if (ts.isInterfaceDeclaration(decl) ||
|
|
116
|
+
ts.isClassDeclaration(decl) ||
|
|
117
|
+
ts.isEnumDeclaration(decl)) {
|
|
118
|
+
if (decl.name) {
|
|
119
|
+
const symbol = checker.getSymbolAtLocation(decl.name);
|
|
120
|
+
if (symbol)
|
|
121
|
+
return checker.getDeclaredTypeOfSymbol(symbol);
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
|
-
// type alias
|
|
106
124
|
if (ts.isTypeAliasDeclaration(decl)) {
|
|
107
125
|
return checker.getTypeFromTypeNode(decl.type);
|
|
108
126
|
}
|
|
109
127
|
}
|
|
110
|
-
// Scope-based fallback
|
|
128
|
+
// 3️⃣ Scope-based fallback
|
|
111
129
|
const symbol = checker
|
|
112
130
|
.getSymbolsInScope(sf, ts.SymbolFlags.Type | ts.SymbolFlags.Alias | ts.SymbolFlags.Interface)
|
|
113
131
|
.find((s) => s.name === name);
|
|
114
132
|
return symbol ? checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
115
133
|
}
|
|
134
|
+
// ──────────────────────────────────────────────
|
|
135
|
+
// ④ AST Utility
|
|
136
|
+
// ──────────────────────────────────────────────
|
|
116
137
|
function _findLocalDeclaration(sf, name) {
|
|
117
138
|
let found;
|
|
118
139
|
(function walk(node) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runtypex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Runtime type guards compiled from TypeScript types.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "KumJungMin",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": ["dist"],
|
|
27
27
|
"scripts": {
|
|
28
|
-
"build": "tsc -p tsconfig.build.json"
|
|
28
|
+
"build": "tsc -p tsconfig.build.json",
|
|
29
|
+
"test": "jest"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"typescript": "^5.6.3",
|