@webergency-utils/typechecker 0.2.1 → 0.2.3
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/README.md +99 -21
- package/dist/engine/generators.js +6 -5
- package/dist/engine/hoister.js +23 -17
- package/dist/engine/resolver.d.ts +1 -1
- package/dist/engine/resolver.js +16 -9
- package/dist/index.d.ts +14 -16
- package/dist/index.js +18 -2
- package/dist/runtime/casing.js +18 -1
- package/dist/runtime/validators.d.ts +48 -15
- package/dist/runtime/validators.js +781 -111
- package/dist/transformer.js +102 -74
- package/package.json +2 -1
package/dist/transformer.js
CHANGED
|
@@ -3,7 +3,15 @@ import { buildValidator, generateHash, buildJsonSchema, objectToAst } from './en
|
|
|
3
3
|
export { buildValidator, generateHash, buildJsonSchema } from './engine/resolver.js';
|
|
4
4
|
import { hoistRegistrations } from './engine/hoister.js';
|
|
5
5
|
import { installStaticConstraintDiagnostics } from './engine/staticAsserts.js';
|
|
6
|
-
const
|
|
6
|
+
const TYPE_FUNCTIONS = ['is', 'assert', 'assertGuard', 'validate', 'jsonSchema'];
|
|
7
|
+
const SCHEMA_FUNCTIONS = ['isSchema', 'assertSchema', 'assertGuardSchema', 'validateSchema'];
|
|
8
|
+
const VALIDATION_FUNCTIONS = ['is', 'assert', 'assertGuard', 'validate'];
|
|
9
|
+
function metadataCall(method, args) {
|
|
10
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), method), undefined, args);
|
|
11
|
+
}
|
|
12
|
+
function argOrUndefined(args, index) {
|
|
13
|
+
return args[index] || ts.factory.createIdentifier('undefined');
|
|
14
|
+
}
|
|
7
15
|
export default function transformer(program) {
|
|
8
16
|
const checker = program.getTypeChecker();
|
|
9
17
|
installStaticConstraintDiagnostics(program);
|
|
@@ -12,88 +20,108 @@ export default function transformer(program) {
|
|
|
12
20
|
const validatorCache = new Map();
|
|
13
21
|
const schemasCache = new Map();
|
|
14
22
|
const requiredUtils = new Set();
|
|
23
|
+
const helperBindings = new Map();
|
|
24
|
+
const helperNamespaces = new Set();
|
|
25
|
+
const hashByTypeId = new Map();
|
|
26
|
+
const getTypeHash = (type) => {
|
|
27
|
+
const typeId = type.id;
|
|
28
|
+
if (typeof typeId === 'number') {
|
|
29
|
+
const cached = hashByTypeId.get(typeId);
|
|
30
|
+
if (cached) {
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
const hash = generateHash(type, checker);
|
|
34
|
+
hashByTypeId.set(typeId, hash);
|
|
35
|
+
return hash;
|
|
36
|
+
}
|
|
37
|
+
return generateHash(type, checker);
|
|
38
|
+
};
|
|
39
|
+
for (const statement of sourceFile.statements) {
|
|
40
|
+
if (!ts.isImportDeclaration(statement)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const moduleName = ts.isStringLiteral(statement.moduleSpecifier)
|
|
44
|
+
? statement.moduleSpecifier.text
|
|
45
|
+
: '';
|
|
46
|
+
const isTypecheckerImport = moduleName === '@webergency-utils/typechecker' ||
|
|
47
|
+
/(?:^|\/)(?:src\/)?index\.(?:js|ts)$/.test(moduleName);
|
|
48
|
+
if (!isTypecheckerImport) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const bindings = statement.importClause?.namedBindings;
|
|
52
|
+
if (bindings && ts.isNamedImports(bindings)) {
|
|
53
|
+
for (const element of bindings.elements) {
|
|
54
|
+
const importedName = element.propertyName?.text || element.name.text;
|
|
55
|
+
if (TYPE_FUNCTIONS.includes(importedName) || SCHEMA_FUNCTIONS.includes(importedName)) {
|
|
56
|
+
helperBindings.set(element.name.text, importedName);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (bindings && ts.isNamespaceImport(bindings)) {
|
|
61
|
+
helperNamespaces.add(bindings.name.text);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
15
64
|
const visitor = (node) => {
|
|
16
|
-
// Skip visiting ImportDeclarations to preserve their original symbols and avoid compiler crashes
|
|
17
65
|
if (ts.isImportDeclaration(node)) {
|
|
18
66
|
return node;
|
|
19
67
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
68
|
+
if (ts.isCallExpression(node)) {
|
|
69
|
+
let fnName;
|
|
70
|
+
if (ts.isIdentifier(node.expression)) {
|
|
71
|
+
fnName = helperBindings.get(node.expression.text);
|
|
72
|
+
}
|
|
73
|
+
else if (ts.isPropertyAccessExpression(node.expression) &&
|
|
74
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
75
|
+
helperNamespaces.has(node.expression.expression.text)) {
|
|
76
|
+
fnName = node.expression.name.text;
|
|
77
|
+
}
|
|
78
|
+
if (fnName && SCHEMA_FUNCTIONS.includes(fnName)) {
|
|
79
|
+
requiredUtils.add('MetadataStore');
|
|
80
|
+
const schemaArg = argOrUndefined(node.arguments, 0);
|
|
81
|
+
const inputArg = argOrUndefined(node.arguments, 1);
|
|
82
|
+
const optionsArg = argOrUndefined(node.arguments, 2);
|
|
83
|
+
const compiled = metadataCall('getOrCompileSchema', [schemaArg]);
|
|
84
|
+
if (fnName === 'validateSchema') {
|
|
85
|
+
return metadataCall('validate', [compiled, inputArg, optionsArg]);
|
|
86
|
+
}
|
|
87
|
+
if (fnName === 'isSchema') {
|
|
88
|
+
return metadataCall('is', [compiled, inputArg, optionsArg]);
|
|
89
|
+
}
|
|
90
|
+
if (fnName === 'assertSchema') {
|
|
91
|
+
return metadataCall('assert', [compiled, inputArg, optionsArg]);
|
|
92
|
+
}
|
|
93
|
+
return metadataCall('assertGuard', [compiled, inputArg, optionsArg]);
|
|
94
|
+
}
|
|
95
|
+
if (fnName && TYPE_FUNCTIONS.includes(fnName)) {
|
|
24
96
|
const typeArg = node.typeArguments?.[0];
|
|
25
97
|
if (typeArg) {
|
|
26
98
|
const type = checker.getTypeFromTypeNode(typeArg);
|
|
27
|
-
const hash =
|
|
28
|
-
if (!validatorCache.has(hash)) {
|
|
29
|
-
buildValidator(type, checker, validatorCache, requiredUtils);
|
|
30
|
-
}
|
|
31
|
-
if (!schemasCache.has(hash)) {
|
|
32
|
-
const schemaObj = buildJsonSchema(type, checker);
|
|
33
|
-
schemasCache.set(hash, objectToAst(schemaObj));
|
|
34
|
-
}
|
|
35
|
-
let getCall;
|
|
36
|
-
const arg0 = node.arguments[0] || ts.factory.createIdentifier('undefined');
|
|
37
|
-
const arg1 = node.arguments[1] || ts.factory.createIdentifier('undefined');
|
|
38
|
-
let hasSchema = false;
|
|
39
|
-
let schemaExpr;
|
|
40
|
-
if (node.arguments[1]) {
|
|
41
|
-
const optArg = node.arguments[1];
|
|
42
|
-
const optType = checker.getTypeAtLocation(optArg);
|
|
43
|
-
const schemaProp = optType.getProperty('schema');
|
|
44
|
-
if (schemaProp) {
|
|
45
|
-
hasSchema = true;
|
|
46
|
-
if (ts.isObjectLiteralExpression(optArg)) {
|
|
47
|
-
const prop = optArg.properties.find(p => {
|
|
48
|
-
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'schema') {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
if (ts.isShorthandPropertyAssignment(p) && p.name.text === 'schema') {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
return false;
|
|
55
|
-
});
|
|
56
|
-
if (prop) {
|
|
57
|
-
if (ts.isPropertyAssignment(prop)) {
|
|
58
|
-
schemaExpr = prop.initializer;
|
|
59
|
-
}
|
|
60
|
-
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
61
|
-
schemaExpr = prop.name;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (!schemaExpr) {
|
|
66
|
-
schemaExpr = ts.factory.createPropertyAccessExpression(optArg, 'schema');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (hasSchema && schemaExpr) {
|
|
71
|
-
const compileAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getOrCompileSchema');
|
|
72
|
-
getCall = ts.factory.createCallExpression(compileAccess, undefined, [schemaExpr]);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'getValidator');
|
|
76
|
-
getCall = ts.factory.createCallExpression(mdStoreAccess, undefined, [ts.factory.createStringLiteral(hash)]);
|
|
77
|
-
}
|
|
99
|
+
const hash = getTypeHash(type);
|
|
78
100
|
if (fnName === 'jsonSchema') {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return ts.factory.
|
|
85
|
-
}
|
|
86
|
-
else if (fnName === 'is') {
|
|
87
|
-
const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'is');
|
|
88
|
-
return ts.factory.createCallExpression(mdStoreAccess, undefined, [getCall, arg0, arg1]);
|
|
89
|
-
}
|
|
90
|
-
else if (fnName === 'assert') {
|
|
91
|
-
const mdStoreAccess = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('MetadataStore'), 'assert');
|
|
92
|
-
return ts.factory.createCallExpression(mdStoreAccess, undefined, [getCall, arg0, arg1]);
|
|
101
|
+
requiredUtils.add('MetadataStore');
|
|
102
|
+
if (!schemasCache.has(hash)) {
|
|
103
|
+
const schemaObj = buildJsonSchema(type, checker);
|
|
104
|
+
schemasCache.set(hash, objectToAst(schemaObj));
|
|
105
|
+
}
|
|
106
|
+
return metadataCall('getSchema', [ts.factory.createStringLiteral(hash)]);
|
|
93
107
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
if (VALIDATION_FUNCTIONS.includes(fnName)) {
|
|
109
|
+
if (!validatorCache.has(hash)) {
|
|
110
|
+
buildValidator(type, checker, validatorCache, requiredUtils, hash);
|
|
111
|
+
}
|
|
112
|
+
const inputArg = argOrUndefined(node.arguments, 0);
|
|
113
|
+
const optionsArg = argOrUndefined(node.arguments, 1);
|
|
114
|
+
const getValidator = metadataCall('getValidator', [ts.factory.createStringLiteral(hash)]);
|
|
115
|
+
if (fnName === 'validate') {
|
|
116
|
+
return metadataCall('validate', [getValidator, inputArg, optionsArg]);
|
|
117
|
+
}
|
|
118
|
+
if (fnName === 'is') {
|
|
119
|
+
return metadataCall('is', [getValidator, inputArg, optionsArg]);
|
|
120
|
+
}
|
|
121
|
+
if (fnName === 'assert') {
|
|
122
|
+
return metadataCall('assert', [getValidator, inputArg, optionsArg]);
|
|
123
|
+
}
|
|
124
|
+
return metadataCall('assertGuard', [getValidator, inputArg, optionsArg]);
|
|
97
125
|
}
|
|
98
126
|
}
|
|
99
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webergency-utils/typechecker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TypeScript compiler plugin for runtime validation",
|
|
6
6
|
"author": "radixxko",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"lint": "eslint .",
|
|
44
44
|
"test": "vitest run",
|
|
45
|
+
"bench": "npm run build && node scripts/perf-microbench.mjs",
|
|
45
46
|
"fuzz": "npm run build:fuzz && jazzer fuzz_typechecker.cjs",
|
|
46
47
|
"coverage": "vitest run --coverage",
|
|
47
48
|
"clean": "rm -rf ./dist ./dist-fuzz",
|