microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.21k stars 12.51k forks source link

Module resolution: NodeNext breaks typechecking #60561

Open damianobarbati opened 2 days ago

damianobarbati commented 2 days ago

Demo Repo

https://github.com/damianobarbati/ts-repro

Which of the following problems are you reporting?

The module specifier resolves to the right file, but something about the types are wrong

Demonstrate the defect described above with a code sample.

To reproduce:

git clone git@github.com:damianobarbati/ts-repro.git
cd ts-repro
git checkout typescript-constructable-issue
pnpm i
pnpm tsc

Here the line where this can be seen: https://github.com/damianobarbati/ts-repro/blob/typescript-constructable-issue/services/api/src/index.ts#L2

Run tsc --showConfig and paste its output here

{
    "compilerOptions": {
        "moduleResolution": "nodenext",
        "module": "nodenext",
        "target": "esnext",
        "lib": [
            "dom",
            "dom.iterable",
            "esnext",
            "webworker"
        ],
        "allowJs": true,
        "allowImportingTsExtensions": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "noUnusedLocals": false,
        "esModuleInterop": true,
        "resolveJsonModule": true,
        "strictPropertyInitialization": false,
        "isolatedModules": true,
        "jsx": "preserve",
        "incremental": true,
        "baseUrl": "./",
        "tsBuildInfoFile": "../../../../tmp/tsbuildinfo",
        "moduleDetection": "force",
        "allowSyntheticDefaultImports": true,
        "resolvePackageJsonExports": true,
        "resolvePackageJsonImports": true,
        "preserveConstEnums": true,
        "useDefineForClassFields": true,
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictBuiltinIteratorReturn": true,
        "alwaysStrict": true,
        "useUnknownInCatchVariables": true
    },
    "files": [
        "./services/api/src/Foe.spec.ts",
        "./services/api/src/Foe.ts",
        "./services/api/src/index.ts",
        "./services/api/src/helper/fn.spec.ts",
        "./services/api/src/helper/fn.ts",
        "./services/types/src/User.spec.ts",
        "./services/types/src/User.ts"
    ]
}

Run tsc --traceResolution and paste its output here

File '/Users/damians/Desktop/ts-repro/services/api/src/package.json' does not exist.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/services/api/package.json'.
======== Resolving module 'ioredis' from '/Users/damians/Desktop/ts-repro/services/api/src/index.ts'. ========
Explicitly specified module resolution kind: 'NodeNext'.
Resolving in ESM mode with conditions 'import', 'types', 'node'.
'baseUrl' option is set to '/Users/damians/Desktop/ts-repro', using this value to resolve non-relative module name 'ioredis'.
Resolving module name 'ioredis' relative to base url '/Users/damians/Desktop/ts-repro' - '/Users/damians/Desktop/ts-repro/ioredis'.
Loading module as file / folder, candidate module location '/Users/damians/Desktop/ts-repro/ioredis', target file types: TypeScript, JavaScript, Declaration, JSON.
Directory '/Users/damians/Desktop/ts-repro/ioredis' does not exist, skipping all lookups in it.
File '/Users/damians/Desktop/ts-repro/services/api/src/package.json' does not exist according to earlier cached lookups.
File '/Users/damians/Desktop/ts-repro/services/api/package.json' exists according to earlier cached lookups.
Loading module 'ioredis' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Directory '/Users/damians/Desktop/ts-repro/services/api/src/node_modules' does not exist, skipping all lookups in it.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/package.json'.
'package.json' does not have a 'typesVersions' field.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field './built/index.d.ts' that references '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts'.
File '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts' exists - use it as a name resolution result.
'package.json' does not have a 'peerDependencies' field.
Resolving real path for '/Users/damians/Desktop/ts-repro/services/api/node_modules/ioredis/built/index.d.ts', result '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/index.d.ts'.
======== Module name 'ioredis' was successfully resolved to '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/index.d.ts' with Package ID 'ioredis/built/index.d.ts@5.4.1'. ========
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/package.json' does not exist.
Found 'package.json' at '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/package.json'.
======== Resolving module './Redis' from '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/index.d.ts'. ========
Explicitly specified module resolution kind: 'NodeNext'.
Resolving in CJS mode with conditions 'require', 'types', 'node'.
Loading module as file / folder, candidate module location '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/Redis', target file types: TypeScript, JavaScript, Declaration, JSON.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/Redis.ts' does not exist.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/Redis.tsx' does not exist.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/Redis.d.ts' exists - use it as a name resolution result.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/package.json' exists according to earlier cached lookups.
'package.json' does not have a 'peerDependencies' field.

....OMISSIS....

======== Module name '@typescript/lib-webworker' was not resolved. ========
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/typescript@5.6.3/node_modules/typescript/lib/package.json' does not exist according to earlier cached lookups.
File '/Users/damians/Desktop/ts-repro/node_modules/.pnpm/typescript@5.6.3/node_modules/typescript/package.json' exists according to earlier cached lookups.
services/api/src/index.ts:2:19 - error TS2351: This expression is not constructable.
  Type 'typeof import("/Users/damians/Desktop/ts-repro/node_modules/.pnpm/ioredis@5.4.1/node_modules/ioredis/built/index")' has no construct signatures.

2 const redis = new Redis();
                    ~~~~~

Found 1 error in services/api/src/index.ts:2

 ELIFECYCLE  Command failed with exit code 1.

Paste the package.json of the importing module, if it exists

{
  "name": "api",
  "type": "module",
  "imports": {
    "#api/*": "./src/*",
    "#api/helper/*": "./src/helper/*"
  },
  "scripts": {
    "test": "vitest run",
    "start:dev": "node --no-warnings --experimental-transform-types --watch ./src/index.ts",
    "start": "node --no-warnings --experimental-transform-types ./src/index.ts"
  },
  "dependencies": {
    "ioredis": "^5.4.1",
    "types": "workspace:^"
  }
}

Paste the package.json of the target module, if it exists

{
  "name": "ioredis",
  "version": "5.4.1",
  "description": "A robust, performance-focused and full-featured Redis client for Node.js.",
  "main": "./built/index.js",
  "types": "./built/index.d.ts",
  "files": [
    "built/"
  ],
  "scripts": {
    "test:tsd": "npm run build && tsd",
    "test:js": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/helpers/*.ts\" \"test/unit/**/*.ts\" \"test/functional/**/*.ts\"",
    "test:cov": "nyc npm run test:js",
    "test:js:cluster": "TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test mocha \"test/cluster/**/*.ts\"",
    "test": "npm run test:js && npm run test:tsd",
    "lint": "eslint --ext .js,.ts ./lib",
    "docs": "npx typedoc --logLevel Error --excludeExternals --excludeProtected --excludePrivate --readme none lib/index.ts",
    "format": "prettier --write \"{,!(node_modules)/**/}*.{js,ts}\"",
    "format-check": "prettier --check \"{,!(node_modules)/**/}*.{js,ts}\"",
    "build": "rm -rf built && tsc",
    "prepublishOnly": "npm run build",
    "semantic-release": "semantic-release"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/luin/ioredis.git"
  },
  "keywords": [
    "redis",
    "cluster",
    "sentinel",
    "pipelining"
  ],
  "tsd": {
    "directory": "test/typing"
  },
  "author": "Zihua Li <i@zihua.li> (http://zihua.li)",
  "license": "MIT",
  "funding": {
    "type": "opencollective",
    "url": "https://opencollective.com/ioredis"
  },
  "dependencies": {
    "@ioredis/commands": "^1.1.1",
    "cluster-key-slot": "^1.1.0",
    "debug": "^4.3.4",
    "denque": "^2.1.0",
    "lodash.defaults": "^4.2.0",
    "lodash.isarguments": "^3.1.0",
    "redis-errors": "^1.2.0",
    "redis-parser": "^3.0.0",
    "standard-as-callback": "^2.1.0"
  },
  "devDependencies": {
    "@ioredis/interface-generator": "^1.3.0",
    "@semantic-release/changelog": "^6.0.1",
    "@semantic-release/commit-analyzer": "^9.0.2",
    "@semantic-release/git": "^10.0.1",
    "@types/chai": "^4.3.0",
    "@types/chai-as-promised": "^7.1.5",
    "@types/debug": "^4.1.5",
    "@types/lodash.defaults": "^4.2.7",
    "@types/lodash.isarguments": "^3.1.7",
    "@types/mocha": "^9.1.0",
    "@types/node": "^14.18.12",
    "@types/redis-errors": "^1.2.1",
    "@types/sinon": "^10.0.11",
    "@typescript-eslint/eslint-plugin": "^5.48.1",
    "@typescript-eslint/parser": "^5.48.1",
    "chai": "^4.3.6",
    "chai-as-promised": "^7.1.1",
    "eslint": "^8.31.0",
    "eslint-config-prettier": "^8.6.0",
    "mocha": "^9.2.1",
    "nyc": "^15.1.0",
    "prettier": "^2.6.1",
    "semantic-release": "^19.0.2",
    "server-destroy": "^1.0.1",
    "sinon": "^13.0.1",
    "ts-node": "^10.4.0",
    "tsd": "^0.19.1",
    "typedoc": "^0.22.18",
    "typescript": "^4.6.3",
    "uuid": "^9.0.0"
  },
  "nyc": {
    "reporter": [
      "lcov"
    ]
  },
  "engines": {
    "node": ">=12.22.0"
  },
  "mocha": {
    "exit": true,
    "timeout": 8000,
    "recursive": true,
    "require": "ts-node/register"
  }
}

Any other comments can go here

I'm using NodeNext to leverage the new node flag --transform-types.

damianobarbati commented 2 days ago

I think we can also assume that as people will switch from tsx-like tools to the native nodejs flag to have typescript, the more we'll see this.