Skip to content

Conversation

@cauthmann
Copy link
Contributor

@cauthmann cauthmann commented Jul 27, 2021

Hello,

Currently, typescript-is generates verbose error messages even for is<T>, where those messages are ignored. This increases code size for no good reason. I don't use shortCircuit in production, because I'm using typescript-is for validating untrusted input.

This set of patches adds an option to generate code returning {} for errors. The default, auto, will keep error messages for assertions, but remove them for is<T> et al, because they are ignored anyway. Setting it to false will skip all error messages for production builds.

All tests pass, but since this is my first time working with typescript transformers, a review is appreciated. Let me know if you need further changes.

Handle empty objects in TypeGuardError.

This requires passing visitorContext through a few more functions.
Since this was a bit of copy/paste code, I refactored it into a single helper function.
also remove a pointless assignment to an intermediate variable before the return.
@cauthmann
Copy link
Contributor Author

cauthmann commented Jul 27, 2021

And here's a code size comparison for a random example. tl;dr: ~50% size reduction of the generated code, ~70% size reduction when using terser.

Type:

interface Foo {
	a: string;
	b: number[];
	c: [string, number];
}

Before (2991 bytes):

object => { var path = ["$"]; function _string(object) { ; if (typeof object !== "string")
        return { message: "validation failed at " + path.join(".") + ": expected a string", path: path.slice(), reason: { type: "string" } };
    else
        return null; } function _number(object) { ; if (typeof object !== "number")
        return { message: "validation failed at " + path.join(".") + ": expected a number", path: path.slice(), reason: { type: "number" } };
    else
        return null; } function sa__number_ea_14(object) { ; if (!Array.isArray(object))
        return { message: "validation failed at " + path.join(".") + ": expected an array", path: path.slice(), reason: { type: "array" } }; for (let i = 0; i < object.length; i++) {
        path.push("[" + i + "]");
        var error = _number(object[i]);
        path.pop();
        if (error)
            return error;
    } return null; } function st__string__number_et_13_14(object) { ; if (!Array.isArray(object) || object.length < 2 || 2 < object.length)
        return { message: "validation failed at " + path.join(".") + ": expected an array with length 2-2", path: path.slice(), reason: { type: "tuple", minLength: 2, maxLength: 2 } }; {
        path.push("[0]");
        var error = _string(object[0]);
        path.pop();
        if (error)
            return error;
    } {
        path.push("[1]");
        var error = _number(object[1]);
        path.pop();
        if (error)
            return error;
    } return null; } function _81(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object))
        return { message: "validation failed at " + path.join(".") + ": expected an object", path: path.slice(), reason: { type: "object" } }; {
        if ("a" in object) {
            path.push("a");
            var error = _string(object["a"]);
            path.pop();
            if (error)
                return error;
        }
        else
            return { message: "validation failed at " + path.join(".") + ": expected 'a' in object", path: path.slice(), reason: { type: "missing-property", property: "a" } };
    } {
        if ("b" in object) {
            path.push("b");
            var error = sa__number_ea_14(object["b"]);
            path.pop();
            if (error)
                return error;
        }
        else
            return { message: "validation failed at " + path.join(".") + ": expected 'b' in object", path: path.slice(), reason: { type: "missing-property", property: "b" } };
    } {
        if ("c" in object) {
            path.push("c");
            var error = st__string__number_et_13_14(object["c"]);
            path.pop();
            if (error)
                return error;
        }
        else
            return { message: "validation failed at " + path.join(".") + ": expected 'c' in object", path: path.slice(), reason: { type: "missing-property", property: "c" } };
    } return null; } return _81(object); }

Before (with terser, 1608 bytes):

e=>{var t=["$"];function a(e){return"string"!=typeof e?{message:"validation failed at "+t.join(".")+": expected a string",path:t.slice(),reason:{type:"string"}}:null}function n(e){return"number"!=typeof e?{message:"validation failed at "+t.join(".")+": expected a number",path:t.slice(),reason:{type:"number"}}:null}return function(e){if("object"!=typeof e||null===e||Array.isArray(e))return{message:"validation failed at "+t.join(".")+": expected an object",path:t.slice(),reason:{type:"object"}};if(!("a"in e))return{message:"validation failed at "+t.join(".")+": expected 'a' in object",path:t.slice(),reason:{type:"missing-property",property:"a"}};t.push("a");var r=a(e.a);return t.pop(),r||("b"in e?(t.push("b"),r=function(e){if(!Array.isArray(e))return{message:"validation failed at "+t.join(".")+": expected an array",path:t.slice(),reason:{type:"array"}};for(let r=0;r<e.length;r++){t.push("["+r+"]");var a=n(e[r]);if(t.pop(),a)return a}return null}(e.b),t.pop(),r||("c"in e?(t.push("c"),r=function(e){if(!Array.isArray(e)||e.length<2||2<e.length)return{message:"validation failed at "+t.join(".")+": expected an array with length 2-2",path:t.slice(),reason:{type:"tuple",minLength:2,maxLength:2}};t.push("[0]");var r=a(e[0]);return t.pop(),r||(t.push("[1]"),r=n(e[1]),t.pop(),r||null)}(e.c),t.pop(),r||null):{message:"validation failed at "+t.join(".")+": expected 'c' in object",path:t.slice(),reason:{type:"missing-property",property:"c"}})):{message:"validation failed at "+t.join(".")+": expected 'b' in object",path:t.slice(),reason:{type:"missing-property",property:"b"}})}(e)};

After (1554 bytes):

object => { function _string(object) { ; if (typeof object !== "string")
        return {};
    else
        return null; } function _number(object) { ; if (typeof object !== "number")
        return {};
    else
        return null; } function sa__number_ea_14(object) { ; if (!Array.isArray(object))
        return {}; for (let i = 0; i < object.length; i++) {
        var error = _number(object[i]);
        if (error)
            return error;
    } return null; } function st__string__number_et_13_14(object) { ; if (!Array.isArray(object) || object.length < 2 || 2 < object.length)
        return {}; {
        var error = _string(object[0]);
        if (error)
            return error;
    } {
        var error = _number(object[1]);
        if (error)
            return error;
    } return null; } function _81(object) { ; if (typeof object !== "object" || object === null || Array.isArray(object))
        return {}; {
        if ("a" in object) {
            var error = _string(object["a"]);
            if (error)
                return error;
        }
        else
            return {};
    } {
        if ("b" in object) {
            var error = sa__number_ea_14(object["b"]);
            if (error)
                return error;
        }
        else
            return {};
    } {
        if ("c" in object) {
            var error = st__string__number_et_13_14(object["c"]);
            if (error)
                return error;
        }
        else
            return {};
    } return null; } return _81(object); }

After with terser (485 bytes):

export default r=>{function n(r){return"string"!=typeof r?{}:null}function t(r){return"number"!=typeof r?{}:null}return function(r){return"object"!=typeof r||null===r||Array.isArray(r)?{}:"a"in r?(u=n(r.a))?u:"b"in r?(u=function(r){if(!Array.isArray(r))return{};for(let u=0;u<r.length;u++){var n=t(r[u]);if(n)return n}return null}(r.b))?u:"c"in r?(u=function(r){return!Array.isArray(r)||r.length<2||2<r.length?{}:(u=n(r[0]))||(u=t(r[1]))?u:null;var u}(r.c))?u:null:{}:{}:{};var u}(r)};

@woutervh- woutervh- merged commit 0dbefd0 into woutervh-:master Nov 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants