Skip to content

Declaration emit references imported type without importing it when inheriting static methods via CommonJS require #63353

@avivkeller

Description

@avivkeller

🔎 Search Terms

declaration emit missing import typedef commonjs require static method

🕗 Version & Regression Information

⏯ Playground Link

N/A, as this requires multiple files

💻 Code

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "allowJs": true,
    "checkJs": false,
    "strict": true,
    "noEmit": false,
    "declaration": true,
    "emitDeclarationOnly": true,
    "declarationDir": "types",
    "rootDir": "lib",
    "skipLibCheck": true
  },
  "include": ["lib/**/*.js"]
}

lib/Parent.js

/** @typedef {import('../Dependency')} DoubleDependency */

class Parent {}

class ParentProperty {
    /**
     * @returns {DoubleDependency}
     */
    static myStaticMethod() {}
}

module.exports = Parent;
Parent.Property = ParentProperty;

lib/Child.js

"use strict";

const Dependency = require("./Parent");

class MyChild {}

MyChild.Template = class Child extends (
    Dependency.Property
) {};

module.exports = MyChild;

🙁 Actual behavior

tsc emits the following types/Child.d.ts:

export = MyChild;
declare class MyChild {
}
declare namespace MyChild {
    export { Child as Template };
}
declare const Child_base: {
    new (): {};
    myStaticMethod(): DoubleDependency;  // ← uses DoubleDependency but it is never imported
};
declare class Child extends Child_base {
}
import Dependency = require("./Parent");  // ← emitted but unused

There are two problems:

  1. DoubleDependency is referenced but never imported. The type flows through the Child_base synthetic constructor type (via ParentProperty.myStaticMethod's return type), but the declaration emitter does not add an import for it.
  2. import Dependency = require("./Parent") is emitted but never referenced in the declaration file. It appears to be a leftover from the const Dependency = require("./Parent") in the source, but since the emitted declarations use an inlined structural type for Child_base instead of referencing Dependency.Property, the import serves no purpose.

The emitted .d.ts is not self-contained (consuming it produces):

Child.d.ts(9,23): error TS2304: Cannot find name 'DoubleDependency'.

This can be reproduced by running:

tsc --ignoreConfig --strict --noEmit --skipLibCheck false --module commonjs --target ES2017 types/Child.d.ts types/Parent.d.ts

🙂 Expected behavior

The emitted types/Child.d.ts should either:

  1. Import DoubleDependency from ./Parent (where it is defined as a type alias), so the reference resolves:

    import Dependency = require("./Parent");
    // ...
    declare const Child_base: {
        new (): {};
        myStaticMethod(): Dependency.DoubleDependency;
    };
  2. Or inline the resolved type (which is any since '../Dependency' doesn't exist) instead of referencing a name that isn't in scope:

    declare const Child_base: {
        new (): {};
        myStaticMethod(): any;
    };

In either case, the unused import Dependency at the bottom should be removed if it is not referenced, or moved to the top if it is.

Additional information about the issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions