Skip to content

Type self-reference in class decorator uses incorrect local variables in generated JS. #9685

Closed
@EricABC

Description

@EricABC

TypeScript Version: 2.0.0

The problem is a common occurrence in Angular 2 where forwardRef is often used. It is included in the example, but removing forwardRef and the angular dependency will produce the same issue.

Code

import { forwardRef } from '@angular/core';

declare var Something: any;
@Something({ v: forwardRef(() => Testing123) })
export class Testing123 {}

Expected behavior:

System.register(['@angular/core'], function(exports_1, context_1) {
    "use strict";
    var __moduleName = context_1 && context_1.id;
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };
    var __metadata = (this && this.__metadata) || function (k, v) {
        if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
    };
    var core_1;
    var Testing123;
    return {
        setters:[
            function (core_1_1) {
                core_1 = core_1_1;
            }],
        execute: function() {


*****
// One option that made this work was adding 'let' here, which makes this simple case work.
            let Testing123_1 = class Testing123 {
            };
*****


            let Testing123 = Testing123_1;
            Testing123 = Testing123_1 = __decorate([
                Something({ v: core_1.forwardRef(() => Testing123) }), 
                __metadata('design:paramtypes', [])
            ], Testing123);
            exports_1("Testing123", Testing123);
        }
    }
});

Actual behavior:

System.register(['@angular/core'], function(exports_1, context_1) {
    "use strict";
    var __moduleName = context_1 && context_1.id;
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };
    var __metadata = (this && this.__metadata) || function (k, v) {
        if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
    };
    var core_1;
    var Testing123;
    return {
        setters:[
            function (core_1_1) {
                core_1 = core_1_1;
            }],
        execute: function() {


*****  // Exception raised here, there is no Testing123_1
            Testing123_1 = class Testing123 {
*****


            };
            let Testing123 = Testing123_1;
            Testing123 = Testing123_1 = __decorate([
                Something({ v: core_1.forwardRef(() => Testing123) }), 
                __metadata('design:paramtypes', [])
            ], Testing123);
            exports_1("Testing123", Testing123);
        }
    }
});

The real-world example that worked in 1.8.10 is here:

import { Component, Input, forwardRef, ViewChild, ElementRef, Renderer } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

@Component({
    selector: "child",
    templateUrl: 'ChildControl.template.html',
    moduleId: __abcModuleReference(__moduleName),
    providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ChildControl), multi: true }]
})
export class ChildControl implements ControlValueAccessor {
    constructor(private _renderer: Renderer) {
    }

    @ViewChild('inputElement') private _inputElement: ElementRef;

    onChange = (_: any) => { };
    onTouched = () => { };

    writeValue(obj: any): void {
        this._renderer.setElementProperty(this._inputElement.nativeElement, 'value', obj);
    }

    registerOnChange(fn: (_: any) => void): void {
        this.onChange = fn;
    }
    registerOnTouched(fn: () => void): void {
        this.onTouched = fn;
    }

    control = new FormControl();
    @Input() value: string;
}

TSConfig targeting systemjs and es6

{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noEmitOnError": false,
    "removeComments": true,
    "target": "es6",
    "inlineSourceMap": true,
    "inlineSources": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "module": "system",
    "skipLibCheck": true
  },
  "exclude": [
    "dist",
    "node_modules",
    "gulpfile.ts",
    "gulp"
  ],
  "compileOnSave": true
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptFixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions