-1

Why is TypeScript reporting the error

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ '&': string; '"': string; "'": string; '<': string; '>': string; }'. No index signature with a parameter of type 'string' was found on type '{ '&': string; '"': string; "'": string; '<': string; '>': string; }'.ts(7053)

in the following Utils.ts file?

export function escapeHtml(text:string) {
    const characters = {
        '&': '&amp;',
        '"': '&quot;',
        "'": '&#039;',
        '<': '&lt;',
        '>': '&gt;'
    };

    return text.replace(/[<>&"']/g, function(x) {
        return characters[x];
    });
}
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
  • It says why - `x` can be any string (the fact that the regex only matches one of five characters isn't information available to the type system) but `characters` only contains a few properties. – jonrsharpe Jun 28 '23 at 15:35

2 Answers2

0

I have fixed my issue with a keyof, but wonder if it is a good style:

const characters = {
    '&': '&amp;',
    '"': '&quot;',
    "'": '&#039;',
    '<': '&lt;',
    '>': '&gt;'
};

export function escapeHtml(text:string) {

    return text.replace(/[<>&"']/g, function(x) {
        return characters[x as keyof typeof characters];
    });
}
Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
  • 1
    You basically did the better equivalent of mine "don't do this" example. There is nothing wrong with `keyof` as the compiler allows it. I just find it better to define map types explicitly, because that way you essentially say in code 'hey, this is not just an object, but some king of mapping' – Agrgg Jun 28 '23 at 15:46
0

This happens because your x argument is of type any, while it must be not only a string but a specific string. For example, stupidly enough this works (please don't do this!):

return text.replace(/[<>&"']/g, function(x: string) {
    return characters[x as '&'|'"'|'"'|'<'|'>'];
});

Your characters object is not a string: string map, it's an object with 5 properties named &, ", ', < and >. So if you want to get one of the object property values, the name you pass into characters[name] must be one of the 5 possible options.

The correct fix is to define your characters object as string: string dictionary, which can be done like this for example:

function escapeHtml(text:string) {
    const characters: Record<string, string> = {
        '&': '&amp;',
        '"': '&quot;',
        "'": '&#039;',
        '<': '&lt;',
        '>': '&gt;'
    };

    return text.replace(/[<>&"']/g, function(x: string) {
        return characters[x];
    });
}

The Record<string, string> is the key piece here.

Also this is probably a duplicate of your question

Agrgg
  • 279
  • 2
  • 5