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
``````

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];
},
});
``````

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
``````

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

Or here's a REPL:

## 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];
},
});
``````

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;
}, '');
};
``````

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];
};
``````

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'
``````

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.

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 😉

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!

Siddharth

Yeah, i did put a disclaimer at the top 😆