typescript-plugin-css-modules icon indicating copy to clipboard operation
typescript-plugin-css-modules copied to clipboard

Erroneous type error with create-react-app and `noUncheckedIndexedAccess`

Open atomanyih opened this issue 4 years ago • 3 comments
trafficstars

Describe the bug If you use the noUncheckedIndexedAccess compiler flag, classnames that are defined will still be typed as string | undefined

To Reproduce Repro repo: https://github.com/atomanyih/ts-css-modules-test

If you npm start or npx tsc you will see this type error:

error TS2345: Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.

12   const className = someFunction(styles.test);
                                    ~~~~~~~~~~~

Expected behavior The generated classname should be defined

Additional context Use-cases where this is annoying:

  • using classnames cx({[styles.someClass]: someCondition}) will error. Workaround: cx(someCondition && styles.someClass) will pass
  • external packages that don't accept undefined
type someFunction = (className: string) => void

someFunction(styles.someClass) // will error

Workaround: someFunction(styles.someClass as string) (kind of whack)

I'm migrating a codebase to use the noUncheckedIndexedAccess flag and this issue is widely present in the codebase. I am able to work around it, but it's not super ideal

atomanyih avatar Oct 13 '21 22:10 atomanyih

Same issue. I thought I might put in a PR, but I'm not sure how this happens as https://github.com/mrmckeb/typescript-plugin-css-modules/blob/82ba03548c3d2193508e1a4dbc47c1aa9b22943c/src/helpers/createExports.ts#L41 seems to just generate modules in the form

declare let classes: {
  class1: string;
  class2: string;
};
export default classes;

So I don't understand why noUncheckedIndexAccess would trip it up.

mikeplus64 avatar Nov 27 '21 06:11 mikeplus64

So I think that when TypeScript merges these declarations

// standard declaration for css modules, e.g. vite/client exports this
declare module "*.module.css" {
  const classes: { readonly [key: string]: string };
  export default classes;
}

and

declare let classes: {
  class1: string;
  class2: string;
};
export default classes;

It turns accessing classes from a property to an "index" access which will return string | undefined under noUncheckedIndexAccess.

mikeplus64 avatar Nov 27 '21 07:11 mikeplus64

Hi @mikeplus64, a limitation of this plugin - and all TypeScript plugins - is that they only work in the IDE.

So there's not really a good solution here sorry. I know that's frustrating to hear, I'd love to see the TypeScript team allow compile-time plugins to provide type information.

I see three possible solutions here:

  1. Generate a d.ts file for each CSS module (there are a few tools that do that via Webpack, etc).
  2. Use optional chaining when accessing keys.
  3. Try using something like ttypescript with a plugin instead of TypeScript.

I've used the first approach before, and it worked well - it just added a lot of files which I didn't like.

mrmckeb avatar Jan 04 '22 07:01 mrmckeb

Is it possible for an option in the plugin to hint to vscode that each class is potentially undefined? That way at least I can get my editor to force me to use optional chaining.

I don't like that my editor will not warn me of a compiler failure.

cephalization avatar Sep 29 '22 18:09 cephalization

Hi! I don't think this PR resolves the issue, since it doesn't override the option from the config. Or am I doing something wrong? Can we add this to troubleshooting?

Screenshot 2023-09-16 at 13 27 52 Screenshot 2023-09-16 at 13 27 56

ddubrava avatar Sep 16 '23 11:09 ddubrava