DEV Community

Cover image for Tired of agonizing errors caused by typos? Let's fix that!
Siddharth
Siddharth

Posted on

Tired of agonizing errors caused by typos? Let's fix that!

Please don't use this in real-world code!

How many times has this happened?

Object.kes(obj) // => ReferenceError
Enter fullscreen mode Exit fullscreen mode

I just hate it when my code is perfect except for one single typo in the middle of nowhere!

Don't you just wish the code just guessed what the correct reference was and just worked?

Well, fear not! I have made that possible, using Proxies!

And since you don't wanna know how it works, and just wanna make it work, here's the source:

const levenshtein = (str1 = '', str2 = '') => {
    const track = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));

    for (let i = 0; i <= str1.length; i += 1) track[0][i] = i;
    for (let j = 0; j <= str2.length; j += 1) track[j][0] = j;

    for (let j = 1; j <= str2.length; j += 1) {
        for (let i = 1; i <= str1.length; i += 1) {
            const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
                track[j][i] = Math.min(
                track[j][i - 1] + 1, // deletion
                track[j - 1][i] + 1, // insertion
                track[j - 1][i - 1] + indicator, // substitution
            );
        }
    }
    return track[str2.length][str1.length];
};
const closestProp = (choices, name) => {
    let lowest = Infinity;
    return choices.reduce((previous, current) => {
        const distance = levenshtein(current, name);
        if (distance < lowest) {
            lowest = distance;
            return current;
        }
        return previous;
    }, '');
};
const autofix = obj => new Proxy(obj, {
    get(obj, prop) {
        if (!(prop in obj)) prop = closestProp(Object.getOwnPropertyNames(obj), prop);
        return obj[prop];
    },
});
Enter fullscreen mode Exit fullscreen mode

That's it!

Object = autofix(Object);

Object.keys({'foo': 'bar'}); // => ['foo']
Object.kys({'foo': 'bar'}); // => ['foo']
Object.key({'foo': 'bar'}); // => ['foo']
// ...

Math = autofix(Math);
Math.PI; // => 3.141592653589793
Math.PIE; // => 3.141592653589793
Enter fullscreen mode Exit fullscreen mode

Don't believe me? Open the DevTools and try it out!

Or here's a REPL:

const levenshtein = (str1 = '', str2 = '') => { const track = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null)); for (let i = 0; i <= str1.length; i += 1) track[0][i] = i; for (let j = 0; j <= str2.length; j += 1) track[j][0] = j; for (let j = 1; j <= str2.length; j += 1) { for (let i = 1; i <= str1.length; i += 1) { const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1; track[j][i] = Math.min( track[j][i - 1] + 1, // deletion track[j - 1][i] + 1, // insertion track[j - 1][i - 1] + indicator, // substitution ); } } return track[str2.length][str1.length]; }; const closestProp = (choices, name) => { let lowest = Infinity; return choices.reduce((previous, current) => { const distance = levenshtein(current, name); if (distance < lowest) { lowest = distance; return current; } return previous; }, ''); }; const autofix = obj => new Proxy(obj, { get(obj, prop) { if (!(prop in obj)) prop = closestProp(Object.getOwnPropertyNames(obj), prop); return obj[prop]; }, }); Math = autofix(Math); Math.PIE;
The formatting may look weird

How it works

This auto fixer is really simple. It does two things:

  1. We listen to property references using a Proxy and send them to a function.
  2. When we get a reference, and the property does not exist on the object, we try to find the closest matching property and return that.

Step #1 is easy, we can use a Proxy to do so:

const autofix = obj => new Proxy(obj, {
    get(obj, prop) {
        if (!(prop in obj)) prop = closestProp(Object.getOwnPropertyNames(obj), prop)
        return obj[prop];
    },
});
Enter fullscreen mode Exit fullscreen mode

We need to define the closestProp function, which is also pretty simple:

const closestProp = (choices, name) => {
    let lowest = Infinity;

    return choices.reduce((previous, current) => {
        const distance = // find difference between words?

        if (distance < lowest) {
            lowest = distance;
            return current;
        }

        return previous;
    }, '');
};
Enter fullscreen mode Exit fullscreen mode

The last part of the puzzle is to find the distance between the numbers. One way of finding the difference is by finding the Levenshtein distance, which is the number of single-character edits we need to make to change one word to another.

This is an implementation of the Levenshtein distance:

const levenshtein = (str1 = '', str2 = '') => {
    const track = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));

    for (let i = 0; i <= str1.length; i += 1) track[0][i] = i;
    for (let j = 0; j <= str2.length; j += 1) track[j][0] = j;

    for (let j = 1; j <= str2.length; j += 1) {
        for (let i = 1; i <= str1.length; i += 1) {
            const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
                track[j][i] = Math.min(
                track[j][i - 1] + 1, // deletion
                track[j - 1][i] + 1, // insertion
                track[j - 1][i - 1] + indicator, // substitution
            );
        }
    }
    return track[str2.length][str1.length];
};
Enter fullscreen mode Exit fullscreen mode

Now we can add this to the closestProp function and we're done!

It's a library!

I polished up the script and uploaded it to The Deno registry here. It's called typosquatter.

Now we can do stuff like:

import typosquatter from 'https://deno.land/x/typosquatter/mod.ts';

let obj = typosquatter({foo: {bar: {baz: 'lol'}}});

console.log(obj.fo.ba.bz.substrng(1)); // => 'ol'
Enter fullscreen mode Exit fullscreen mode

It's recursive and also works for primitives!!


Once again, please don't use this in real life. Actually, maybe you can because it's very little overhead for solving millions of problems.

Do you think you would use this IRL? If you do, leave a comment!

And if you like the post, give this a ❤️ or a 🦄, or share this on Twitter and spread the love!

Discussion (2)

Collapse
auroratide profile image
Timothy Foster

Nice, I've never seen this kind of approach to this problem before, very creative!

I wouldn't use it in part because tools exist to correct the error immediately or catch it at build-time, but I do find it interesting how this, in a way, "humanizes" computers.

As in, people have a sort of built-in autofix in our brains; if I were to say, "Last night, I ate some chicken fried dice," even though "dice" is an actual word most would autofix that to "rice" in their heads.

So this is kinda like that but for computers 😉

Collapse
miketalbot profile image
Mike Talbot

It's a very cool experiment, but I think it would blow up your eslint and be very hard for other developers to be sure exactly what was being called. Also, for instance, if you used it and then added a property that became a closer levenshtein distance to one you were already (mis)using - the code would break in random places haha!