Typescript: in namespace can't use outside same name variable.

Created on 16 Nov 2016  路  7Comments  路  Source: microsoft/TypeScript

TypeScript Version: 2.0.6(VSCode 1.7.1 included)

Code
test.ts

const abc = {};

function foo(){}

namespace foo {
  export let abc = abc; // <- [ts] Block-scoped variable 'abc' used before its declaration.
}

foo.abc = abc; // <- here is the right reference to `const abc = {}`

Expected behavior:

In namespace foo, I can assign outside abc to namespace foo

Actual behavior:

image

Error: [ts] Block-scoped variable 'abc' used before its declaration.

And if I use foo.abc = abc, the right hand abc is correct reference for const abc = {}

Question

Most helpful comment

  1. Why do the local variable and property on the function need to be the same name? That's the source of your problems with the namespace approach.
  2. I wouldn't try to run a linter on the JavaScript output of the TypeScript compiler. It does some weird things. Use https://github.com/palantir/tslint/ on your TypeScript files instead.
  3. I believe the only way to attach properties to functions and keep type safety is to use declaration merging, which is your namespace approach, or leverage the fact that types can have call signatures you can attach other properties to:
interface callable {
    (x: number): number;
    property: string;
}

let bar: callable;

bar(0); //ok
bar.property = 'x'; //ok

If you just want the property on the function and don't care about accessing it later with type safety, you can cast to any and attach it.

function foo() { }
(foo as any).asdf = 42;

To your last example, this should be close. It uses a type with a call signature.

function a<M>(m: M) {
    interface returnType {
        (p: any): void;
        p?: any;
        m: M
    }

    let b = function (p) {
        b.p = p;
    } as returnType;

    b.m = m;
    return b;
}

let foo = a(42);
foo.m === 42;
foo('');
foo.p === '';

All 7 comments

Is it designed?

The only way to do it right now is

const abc = {}
function foo(){}

namespace foo {
  export let abc;
  export let def = 'def';
}

foo.abc = abc;

But it's not clear. I don't know why def can define and assign together, but abc can't.

The behavior reproduces with just this:

let abc = abc;

The problem in your full example is let abc = ... creates a new variable in the scope of foo -- namespaces create new scopes in TypeScript. It shadows, or overrides, the abc from the outer scope. So it thinks you're trying to initialize a variable with itself. It has no idea there's another abc you're trying to reference above it.

Initializing a value with itself is allowed with var, yielding undefined, but throws an error with block-scoped variables, both in the compiler and in a JavaScript runtime.

@jwbay I just want to do this in javascript

export let abc = {};

function foo(){}

foo.abc = abc;

export foo;

If I do not use namepspace in typescript, the type chcker will throw a error/warning:
[ts] Property 'abc' does not exist on type 'typeof foo'.

And a new problem

const abc = {};

function foo(){}

namespace foo {
  export let abc;
  export let def = 'def';
}

foo.abc = abc;

will emit javascript

var abc = {};
function foo() { }
var foo;
(function (foo) {
    foo.def = 'def';
})(foo || (foo = {}));
foo.abc = abc;

ESLint throw the error [eslint] 'foo' is already defined. (no-redeclare). Why function foo and var foo both emitted here?

A further more question.

If I want to assign a property to a function, but the function is not top level. Then the namespace usage will lead to error [ts] A namespace declaration is only allowed in a namespace or module..

How can I assign a property to a function in this condition?

function a(m, n){
  function b(p){
    b.p = p
  }
  b.m = m;

  return b;
}
  1. Why do the local variable and property on the function need to be the same name? That's the source of your problems with the namespace approach.
  2. I wouldn't try to run a linter on the JavaScript output of the TypeScript compiler. It does some weird things. Use https://github.com/palantir/tslint/ on your TypeScript files instead.
  3. I believe the only way to attach properties to functions and keep type safety is to use declaration merging, which is your namespace approach, or leverage the fact that types can have call signatures you can attach other properties to:
interface callable {
    (x: number): number;
    property: string;
}

let bar: callable;

bar(0); //ok
bar.property = 'x'; //ok

If you just want the property on the function and don't care about accessing it later with type safety, you can cast to any and attach it.

function foo() { }
(foo as any).asdf = 42;

To your last example, this should be close. It uses a type with a call signature.

function a<M>(m: M) {
    interface returnType {
        (p: any): void;
        p?: any;
        m: M
    }

    let b = function (p) {
        b.p = p;
    } as returnType;

    b.m = m;
    return b;
}

let foo = a(42);
foo.m === 42;
foo('');
foo.p === '';

Thanks very much for your patient and detailed answer help me to understand TypeScript more. 馃槃

Was this page helpful?
0 / 5 - 0 ratings