Skip to content

Decorated classes in TS 5.x can't be used in the same file #53332

Closed
@ehaynes99

Description

@ehaynes99

Bug Report

When using the new ts 5 decorators, decorated classes aren't usable within the same file. Compiling with the default tsconfig.json created by tsc --init demonstrates the issue. In the compiled output, the decorated class is added to the exports via an anonymous function expression, but there is no reference to it created in scope for use elsewhere in the file.

Full Compled Output
"use strict";
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
  function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
  var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
  var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
  var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
  var _, done = false;
  for (var i = decorators.length - 1; i >= 0; i--) {
      var context = {};
      for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
      for (var p in contextIn.access) context.access[p] = contextIn.access[p];
      context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
      var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
      if (kind === "accessor") {
          if (result === void 0) continue;
          if (result === null || typeof result !== "object") throw new TypeError("Object expected");
          if (_ = accept(result.get)) descriptor.get = _;
          if (_ = accept(result.set)) descriptor.set = _;
          if (_ = accept(result.init)) initializers.push(_);
      }
      else if (_ = accept(result)) {
          if (kind === "field") initializers.push(_);
          else descriptor[key] = _;
      }
  }
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
  done = true;
};
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
  var useValue = arguments.length > 2;
  for (var i = 0; i < initializers.length; i++) {
      value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
  }
  return useValue ? value : void 0;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Example = void 0;
function loggedMethod(target, context) {
  const methodName = String(context.name);
  function replacementMethod(...args) {
      console.log(`LOG: Entering method '${methodName}'.`);
      const result = target.call(this, ...args);
      console.log(`LOG: Exiting method '${methodName}'.`);
      return result;
  }
  return replacementMethod;
}
exports.Example = (() => {
  var _a;
  let _instanceExtraInitializers = [];
  let _getSomething_decorators;
  return _a = class Example {
          getSomething() {
              return Math.random();
          }
          constructor() {
              __runInitializers(this, _instanceExtraInitializers);
          }
      },
      (() => {
          _getSomething_decorators = [loggedMethod];
          __esDecorate(_a, null, _getSomething_decorators, { kind: "method", name: "getSomething", static: false, private: false, access: { has: obj => "getSomething" in obj, get: obj => obj.getSomething } }, null, _instanceExtraInitializers);
      })(),
      _a;
})();
const example = new Example();
console.log(example.getSomething());
console.log(example.getSomething());
console.log(example.getSomething());
exports.Example = (() => {
    var _a;
    let _instanceExtraInitializers = [];
    let _getSomething_decorators;
    return _a = class Example {
            getSomething() {
                return Math.random();
            }
            constructor() {
                __runInitializers(this, _instanceExtraInitializers);
            }
        },
        (() => {
            _getSomething_decorators = [loggedMethod];
            __esDecorate(_a, null, _getSomething_decorators, { kind: "method", name: "getSomething", static: false, private: false, access: { has: obj => "getSomething" in obj, get: obj => obj.getSomething } }, null, _instanceExtraInitializers);
        })(),
        _a;
})();

Instead, this should probably emit something like:

var Example = (() => {
    // ...
})();
exports.Example = Example;

🔎 Search Terms

decorated class not defined

🕗 Version & Regression Information

typescript@5.0.2. This is the new form of decorator introduced in TS 5.x, so not a regression per se

💻 Code

This is just using the example decorator from the release announcement. It doesn't matter if the decorator is in the same file or not.

function loggedMethod<This, Args extends any[], Return>(
  target: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,
) {
  const methodName = String(context.name)

  function replacementMethod(this: This, ...args: Args): Return {
    console.log(`LOG: Entering method '${methodName}'.`)
    const result = target.call(this, ...args)
    console.log(`LOG: Exiting method '${methodName}'.`)
    return result
  }

  return replacementMethod
}

export class Example {
  @loggedMethod
  getSomething() {
    return Math.random()
  }
}

const example = new Example()
console.log(example.getSomething())
console.log(example.getSomething())
console.log(example.getSomething())

🙁 Actual behavior

/home/ehaynes99/tmp/ts5-test/dist/index.js:66
const example = new Example();
                ^
ReferenceError: Example is not defined

🙂 Expected behavior

The class would be available for use within the same file.

Metadata

Metadata

Assignees

Labels

Fix AvailableA PR has been opened for this issueNeeds InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions