TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

TSC emits invalid .d.ts file with reserved keywords in identifier position

Open TiddoLangerak opened this issue 2 years ago β€’ 3 comments

Bug Report

πŸ”Ž Search Terms

delete reserved keyword .d.ts .d.ts reserved keyword

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about invalid .d.ts types

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

const obj1 = {
    delete: "foobar"
};

const obj2 = {
  x: obj1.delete
};

export default obj2;

πŸ™ Actual behavior

The emitted .d.ts file contains delete in an identifier position. This in turns triggers an error when consumed: Identifier expected. 'delete' is a reserved word that cannot be used here.

Generated .d.ts:

export default obj2;
declare namespace obj2 {
    import x = obj1.delete;
    export { x };
}
declare namespace obj1 {
    const _delete: string;
    export { _delete as delete };
}

πŸ™‚ Expected behavior

The generated .d.ts file should be valid.


Further notes

  • Invalid .d.ts files are generated both if the input is a .js or .ts file
  • I did not find any combination of flags/options/versions that would generate a valid .d.ts file.
  • I did find a workaround by altering the source file πŸ‘‡

Workaround

This issue can currently be worked around by using ["delete"] instead of .delete. E.g. this:

const obj1 = {
    delete: "foobar"
};

const obj2 = {
  x: obj1["delete"]
};

export default obj2;

Generates the following valid .d.ts:

export default obj2;
declare namespace obj2 {
    const x: string;
}

TiddoLangerak avatar Mar 06 '23 11:03 TiddoLangerak

I think instead of modifying the declaration emitter (since there's not really an otherwise-legal thing to emit here), we can change the parse rule on this line to allow reserved words, since we know we can emit obj1['delete'] even when this occurs in non-ambient contexts

import x = obj1.delete;

RyanCavanaugh avatar Mar 06 '23 17:03 RyanCavanaugh

Wait why is { x: obj1.delete } allowed but import x = obj1.delete isn’t

fatcerberus avatar Mar 06 '23 17:03 fatcerberus

In ES3, x.delete (or other reserved words) was illegal, so there's some logic around that which is incorrectly being applied here

RyanCavanaugh avatar Mar 06 '23 17:03 RyanCavanaugh

Updating to TypeScript v5.4.2 has resulted in the error:

Identifier expected. 'delete' is a reserved word that cannot be used here. ts(1359)

In places like this in JSDoc where you link to a property called delete:

https://github.com/jaydenseric/graphql-react/blob/5525aca4e5ba5aad5115e56de92bf5ab4acbd2c9/cacheDelete.mjs#L11

E.g:

/** {@link Foo.delete `delete`} */

This (bug?) is a breaking change in a TypeScript semver minor release. It's not clear yet how to workaround the issue, without no longer linking these things.

jaydenseric avatar Mar 12 '24 09:03 jaydenseric