5

I have

import type { SomeType } from '../../types/Project';
// import { SomeType } from '../../types/Project'; tried this too

const SomeType = ({ s }: { s: SomeType }): JSX.Element => {
  return <>${s}</>;
};

in Project.ts I have

export type SomeType = JSX.Element;

Why does the TS linter not show any warnings, but it will throw an error:

Identifier 'SomeType' has already been declared.

I thought types and variables can coexist with the same name?

Example: https://codesandbox.io/s/objective-glade-y94j58?file=/src/App.tsx

Artem Arkhipov
  • 7,025
  • 5
  • 30
  • 52
supersize
  • 13,764
  • 18
  • 74
  • 133
  • 1
    Types and variables [can indeed](https://tsplay.dev/mx88bm) coexist with the same name. Could you provide a [mre] that others can use to see your error for themselves? If not a self-contained plaintext example, then a link to a web IDE project that shows it? Otherwise it's hard to diagnose. – jcalz Oct 14 '22 at 14:59
  • @jcalz how can the example above be more minimal than this? – supersize Oct 14 '22 at 15:02
  • 3
    This is minimal, but not reproducible. There is a difference. – kelsny Oct 14 '22 at 15:02
  • [This is a minimal reproducible example.](https://codesandbox.io/s/objective-glade-y94j58?file=/src/App.tsx) – kelsny Oct 14 '22 at 15:04
  • @caTS thanks, if you don't mind I have added this to my OP with your credit. – supersize Oct 14 '22 at 15:07
  • I built the same thing locally with rollup and it compiles fine. I would say there is something about `create-react-app` not understanding `import type` the way we would expect. – geoffrey Oct 14 '22 at 17:18
  • I would guess it's a webpack issue, but I'm not sure. – Guillaume Brunerie Oct 14 '22 at 17:46
  • @geoffrey interesting. Can you do a sandbox example? I think this will be helpful to add to the OP. – supersize Oct 15 '22 at 09:32
  • @supersize I added the set up as an answer. I imagine code sandbox adds layers of tools on top of your code. – geoffrey Oct 15 '22 at 10:54

3 Answers3

1

While the solution @Joel provided is a possible solution, I do not think it correctly answers the question by saying "No".

Have a look at the answers here

The answer to your question is YES you can have a type & variable with the same name since TS typing will be removed at compile time (unlike variables).

What you are looking for is the following ESLint Rule: no-shadow

// .eslintrc.js
module.exports = {
  "rules": {
    ...<your_other_rules>,
    // Note: you must disable the base rule as it can report incorrect errors
    "no-shadow": "off",
    "@typescript-eslint/no-shadow": ["off", { ignoreTypeValueShadow: true, ignoreFunctionTypeParameterNameValueShadow: true }]
  }
};
lbragile
  • 7,549
  • 3
  • 27
  • 64
  • @Ibragile this is not a linting issue only. The app actually throws an exception as seen in my OP. Tried your config and it still throws `Identifier 'SomeType' has already been declared.` – supersize Oct 19 '22 at 09:34
  • 2
    @supersize we need more info about the configuration of your project. @caTS sandbox example helped but it's their configuration, not yours. The app can't throw a an exception at runtime for a type error, it's the tooling around it. In the sandbox the message "This error overlay is powered by `react-error-overlay` used in `create-react-app`" is a giveaway. It has to be configured with a lint rule that is more conservative than Typescript. – geoffrey Oct 19 '22 at 09:41
  • @geoffrey exactly, that's what I was trying to say with my comment. An ESlint config does not throw an error. Let me double-check my config and update the OP accordingly. – supersize Oct 19 '22 at 10:18
  • 2
    It could very well be that `create-react-app` reads the lint rules and acts upon them by throwing errors at runtime for convenience. It's been a while since I have used it, I much prefer to control my developing environment rather than using a ready made framework which I don't fully understand. – geoffrey Oct 19 '22 at 11:07
  • @geoffrey it is throwing an error also in my environment, a NextJS app with little to no adjustments in the `next.config` (which is basically the webpack config extended) – supersize Oct 19 '22 at 13:50
0

Simple answer, nope

Even if it's declared as a type, SomeType needs to have an available identifier (aka a name not already in use in the current scope).

If you can not change the name on the type or on the variable, you can alias it.

import type { SomeType as SameTypeWithOtherName } from '../../types/Project';

const SomeType = ({ s }: { s: SameTypeWithOtherName }): JSX.Element => {
  return <>${s}</>;
};
Joel
  • 1,187
  • 1
  • 6
  • 15
  • Try this with `tsc`: `import type { Foo } from './foo'; const Foo = 1;` with `export type Foo = string;` in `foo.ts`. – geoffrey Oct 19 '22 at 09:08
  • @Joel I believe this is not the whole truth. If that what you're describing is correct, why is it possible to set `type` and `const` to the same name in the same file without erroring. Why is it specifically for the `import` only throwing an error? – supersize Oct 19 '22 at 13:58
-2

Following up the discussion in the comments, I'm sharing a config with the same source code as @caTS that does not reproduce the problem, but with rollup as a bundler.

This set up does not simply turn off type safety, I get compile errors from @rollup/plugin-typescript when applicable.

The folder structure is the following:

public/
   - dist/
   - index.html
src/
   - App.tsx
   - index.tsx
   - types.ts
- package.json
- tsconfig.json
- .babelrc
- rollup.config.js

package.json

{
  "type": "module",
  "scripts": {
    "server": "http-server",
    "build/watch": "rollup --config --watch"
  },
  "dependencies": {
    "react": "18.0.0",
    "react-dom": "18.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.18.13",
    "@babel/preset-react": "^7.18.6",
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-commonjs": "^22.0.2",
    "@rollup/plugin-node-resolve": "^13.3.0",
    "@rollup/plugin-replace": "^5.0.0",
    "@rollup/plugin-typescript": "^9.0.1",
    "@types/react": "^18.0.21",
    "@types/react-dom": "^18.0.6",
    "http-server": "^14.1.1",
    "tslib": "^2.4.0",
    "typescript": "^4.8.3"
  }
}

tsconfig.json

{
    "compilerOptions": {
        "rootDir": "./src",
        "moduleResolution": "node",
        "module": "ES2020",
        "target": "ES2020",
        "strictFunctionTypes": true,
        "noImplicitAny": true,
        "strict": true,
        "jsx": "react-jsx"
    },
}

.babelrc

{
    "presets": ["@babel/preset-react"]
}

rollup.config.js

import { nodeResolve } from '@rollup/plugin-node-resolve';
import { babel } from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';

const environment = 'development';

const plugins = [
    replace({
        'process.env.NODE_ENV': JSON.stringify(environment),
        preventAssignment: true
    }),
    typescript({
        target: 'es5',
        module: 'ESNext',
     }),
    babel({
        babelHelpers: 'bundled',
        exclude: [/\/node_modules\//]
    }),
    commonjs(),
    nodeResolve(),
];

export default [{
    input: './src/index.tsx',
    output: {
        file: './public/dist/index.js',
        sourcemap: true,
        format: 'es'
    },
    plugins
}];

src/App.tsx

import type { SomeType } from "./types";

const SomeType = ({ s }: { s: SomeType }): JSX.Element => {
    return <>${s}</>;
};

export default function App() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
      </div>
    );
  }

src/index.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement!);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

src/types.ts

export type SomeType = JSX.Element

public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Test</title>
</head>
<body>
    <div id="root"></div>
    <script type="text/javascript" src="./dist/index.js"></script>
</body>
</html>
geoffrey
  • 2,080
  • 9
  • 13
  • Can you clarify how this answers the question? – kelsny Oct 18 '22 at 14:15
  • 1
    From @rollup/plugin-typescript doc, "`noEmitOnError` (Default: false) If a type error is detected, the Rollup build is aborted when this option is set to true." So it seems that your config will not really perform type checking (and it's pretty usual for bundler, it's quicker and type checking is already done in editors) – Joel Oct 18 '22 at 21:50
  • 1- it does not pretend to answer the question, it's following up the discussion in comments. 2- I do get compile errors with this set up. All of this is written in the goddam text. I don't mean to be rude but that kind of interaction when you spent time helping someone really does not make me want to contribute to stackoverflow – geoffrey Oct 19 '22 at 08:54