disallow references to local variables of the function from parameter initializers
This commit is contained in:
parent
bc6d6ea49a
commit
c36c074f37
|
@ -14174,28 +14174,54 @@ namespace ts {
|
|||
const func = getContainingFunction(node);
|
||||
visit(node.initializer);
|
||||
|
||||
function visit(n: Node) {
|
||||
if (n.kind === SyntaxKind.Identifier) {
|
||||
const referencedSymbol = getNodeLinks(n).resolvedSymbol;
|
||||
function visit(n: Node): void {
|
||||
if (isTypeNode(n) || isDeclarationName(n)) {
|
||||
// do not dive in types
|
||||
// skip declaration names (i.e. in object literal expressions)
|
||||
return;
|
||||
}
|
||||
if (n.kind === SyntaxKind.PropertyAccessExpression) {
|
||||
// skip property names in property access expression
|
||||
return visit((<PropertyAccessExpression>n).expression);
|
||||
}
|
||||
else if (n.kind === SyntaxKind.Identifier) {
|
||||
// check FunctionLikeDeclaration.locals (stores parameters\function local variable)
|
||||
// if it contains entry with a specified name and if this entry matches the resolved symbol
|
||||
if (referencedSymbol && referencedSymbol !== unknownSymbol && getSymbol(func.locals, referencedSymbol.name, SymbolFlags.Value) === referencedSymbol) {
|
||||
if (referencedSymbol.valueDeclaration.kind === SyntaxKind.Parameter) {
|
||||
if (referencedSymbol.valueDeclaration === node) {
|
||||
// if it contains entry with a specified name
|
||||
const symbol = getSymbol(func.locals, (<Identifier>n).text, SymbolFlags.Value);
|
||||
if (!symbol || symbol === unknownSymbol) {
|
||||
return;
|
||||
}
|
||||
if (symbol.valueDeclaration === node) {
|
||||
error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name));
|
||||
return;
|
||||
}
|
||||
if (referencedSymbol.valueDeclaration.pos < node.pos) {
|
||||
// legal case - parameter initializer references some parameter strictly on left of current parameter declaration
|
||||
if (symbol.valueDeclaration.kind === SyntaxKind.Parameter) {
|
||||
// it is ok to reference parameter in initializer if either
|
||||
// - parameter is located strictly on the left of current parameter declaration
|
||||
if (symbol.valueDeclaration.pos < node.pos) {
|
||||
return;
|
||||
}
|
||||
// fall through to error reporting
|
||||
// - parameter is wrapped in function-like entity
|
||||
let current = n;
|
||||
while (current !== node.initializer) {
|
||||
if (isFunctionLike(current.parent)) {
|
||||
return;
|
||||
}
|
||||
// computed property names/initializers in instance property declaration of class like entities
|
||||
// are executed in constructor and thus deferred
|
||||
if (current.parent.kind === SyntaxKind.PropertyDeclaration &&
|
||||
!(current.parent.flags & NodeFlags.Static) &&
|
||||
isClassLike(current.parent.parent)) {
|
||||
return;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
// fall through to report error
|
||||
}
|
||||
error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(<Identifier>n));
|
||||
}
|
||||
}
|
||||
else {
|
||||
forEachChild(n, visit);
|
||||
return forEachChild(n, visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
//// [capturedParametersInInitializers1.ts]
|
||||
// ok - usage is deferred
|
||||
function foo1(y = class {c = x}, x = 1) {
|
||||
new y().c;
|
||||
}
|
||||
|
||||
// ok - used in file
|
||||
function foo2(y = function(x: typeof z) {}, z = 1) {
|
||||
|
||||
}
|
||||
|
||||
// ok -used in type
|
||||
let a;
|
||||
function foo3(y = { x: <typeof z>a }, z = 1) {
|
||||
|
||||
}
|
||||
|
||||
//// [capturedParametersInInitializers1.js]
|
||||
// ok - usage is deferred
|
||||
function foo1(y, x) {
|
||||
if (y === void 0) { y = (function () {
|
||||
function class_1() {
|
||||
this.c = x;
|
||||
}
|
||||
return class_1;
|
||||
}()); }
|
||||
if (x === void 0) { x = 1; }
|
||||
new y().c;
|
||||
}
|
||||
// ok - used in file
|
||||
function foo2(y, z) {
|
||||
if (y === void 0) { y = function (x) { }; }
|
||||
if (z === void 0) { z = 1; }
|
||||
}
|
||||
// ok -used in type
|
||||
var a;
|
||||
function foo3(y, z) {
|
||||
if (y === void 0) { y = { x: a }; }
|
||||
if (z === void 0) { z = 1; }
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
=== tests/cases/compiler/capturedParametersInInitializers1.ts ===
|
||||
// ok - usage is deferred
|
||||
function foo1(y = class {c = x}, x = 1) {
|
||||
>foo1 : Symbol(foo1, Decl(capturedParametersInInitializers1.ts, 0, 0))
|
||||
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14))
|
||||
>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
|
||||
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32))
|
||||
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 1, 32))
|
||||
|
||||
new y().c;
|
||||
>new y().c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
|
||||
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 1, 14))
|
||||
>c : Symbol((Anonymous class).c, Decl(capturedParametersInInitializers1.ts, 1, 25))
|
||||
}
|
||||
|
||||
// ok - used in file
|
||||
function foo2(y = function(x: typeof z) {}, z = 1) {
|
||||
>foo2 : Symbol(foo2, Decl(capturedParametersInInitializers1.ts, 3, 1))
|
||||
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 6, 14))
|
||||
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 6, 27))
|
||||
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43))
|
||||
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 6, 43))
|
||||
|
||||
}
|
||||
|
||||
// ok -used in type
|
||||
let a;
|
||||
>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3))
|
||||
|
||||
function foo3(y = { x: <typeof z>a }, z = 1) {
|
||||
>foo3 : Symbol(foo3, Decl(capturedParametersInInitializers1.ts, 11, 6))
|
||||
>y : Symbol(y, Decl(capturedParametersInInitializers1.ts, 12, 14))
|
||||
>x : Symbol(x, Decl(capturedParametersInInitializers1.ts, 12, 19))
|
||||
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37))
|
||||
>a : Symbol(a, Decl(capturedParametersInInitializers1.ts, 11, 3))
|
||||
>z : Symbol(z, Decl(capturedParametersInInitializers1.ts, 12, 37))
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
=== tests/cases/compiler/capturedParametersInInitializers1.ts ===
|
||||
// ok - usage is deferred
|
||||
function foo1(y = class {c = x}, x = 1) {
|
||||
>foo1 : (y?: typeof (Anonymous class), x?: number) => void
|
||||
>y : typeof (Anonymous class)
|
||||
>class {c = x} : typeof (Anonymous class)
|
||||
>c : number
|
||||
>x : number
|
||||
>x : number
|
||||
>1 : number
|
||||
|
||||
new y().c;
|
||||
>new y().c : number
|
||||
>new y() : (Anonymous class)
|
||||
>y : typeof (Anonymous class)
|
||||
>c : number
|
||||
}
|
||||
|
||||
// ok - used in file
|
||||
function foo2(y = function(x: typeof z) {}, z = 1) {
|
||||
>foo2 : (y?: (x: number) => void, z?: number) => void
|
||||
>y : (x: number) => void
|
||||
>function(x: typeof z) {} : (x: number) => void
|
||||
>x : number
|
||||
>z : number
|
||||
>z : number
|
||||
>1 : number
|
||||
|
||||
}
|
||||
|
||||
// ok -used in type
|
||||
let a;
|
||||
>a : any
|
||||
|
||||
function foo3(y = { x: <typeof z>a }, z = 1) {
|
||||
>foo3 : (y?: { x: number; }, z?: number) => void
|
||||
>y : { x: number; }
|
||||
>{ x: <typeof z>a } : { x: number; }
|
||||
>x : number
|
||||
><typeof z>a : number
|
||||
>z : number
|
||||
>a : any
|
||||
>z : number
|
||||
>1 : number
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
tests/cases/compiler/capturedParametersInInitializers2.ts(1,36): error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it.
|
||||
tests/cases/compiler/capturedParametersInInitializers2.ts(4,26): error TS1166: A computed property name in a class property declaration must directly refer to a built-in symbol.
|
||||
|
||||
|
||||
==== tests/cases/compiler/capturedParametersInInitializers2.ts (2 errors) ====
|
||||
function foo(y = class {static c = x}, x = 1) {
|
||||
~
|
||||
!!! error TS2373: Initializer of parameter 'y' cannot reference identifier 'x' declared after it.
|
||||
y.c
|
||||
}
|
||||
function foo2(y = class {[x] = x}, x = 1) {
|
||||
~~~
|
||||
!!! error TS1166: A computed property name in a class property declaration must directly refer to a built-in symbol.
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
//// [capturedParametersInInitializers2.ts]
|
||||
function foo(y = class {static c = x}, x = 1) {
|
||||
y.c
|
||||
}
|
||||
function foo2(y = class {[x] = x}, x = 1) {
|
||||
}
|
||||
|
||||
//// [capturedParametersInInitializers2.js]
|
||||
function foo(y, x) {
|
||||
if (y === void 0) { y = (function () {
|
||||
function class_1() {
|
||||
}
|
||||
class_1.c = x;
|
||||
return class_1;
|
||||
}()); }
|
||||
if (x === void 0) { x = 1; }
|
||||
y.c;
|
||||
}
|
||||
function foo2(y, x) {
|
||||
if (y === void 0) { y = (function () {
|
||||
function class_2() {
|
||||
this[x] = x;
|
||||
}
|
||||
return class_2;
|
||||
}()); }
|
||||
if (x === void 0) { x = 1; }
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
tests/cases/compiler/functionLikeInParameterInitializer.ts(2,34): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
tests/cases/compiler/functionLikeInParameterInitializer.ts(6,44): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
tests/cases/compiler/functionLikeInParameterInitializer.ts(11,50): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
tests/cases/compiler/functionLikeInParameterInitializer.ts(16,41): error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
|
||||
|
||||
==== tests/cases/compiler/functionLikeInParameterInitializer.ts (4 errors) ====
|
||||
// error
|
||||
export function bar(func = () => foo) {
|
||||
~~~
|
||||
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
let foo = "in";
|
||||
}
|
||||
// error
|
||||
export function baz1(func = { f() { return foo } }) {
|
||||
~~~
|
||||
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz2(func = function () { return foo }) {
|
||||
~~~
|
||||
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz3(func = class { x = foo }) {
|
||||
~~~
|
||||
!!! error TS2373: Initializer of parameter 'func' cannot reference identifier 'foo' declared after it.
|
||||
let foo = "in";
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//// [functionLikeInParameterInitializer.ts]
|
||||
// error
|
||||
export function bar(func = () => foo) {
|
||||
let foo = "in";
|
||||
}
|
||||
// error
|
||||
export function baz1(func = { f() { return foo } }) {
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz2(func = function () { return foo }) {
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz3(func = class { x = foo }) {
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
|
||||
//// [functionLikeInParameterInitializer.js]
|
||||
"use strict";
|
||||
// error
|
||||
function bar(func) {
|
||||
if (func === void 0) { func = function () { return foo; }; }
|
||||
var foo = "in";
|
||||
}
|
||||
exports.bar = bar;
|
||||
// error
|
||||
function baz1(func) {
|
||||
if (func === void 0) { func = { f: function () { return foo; } }; }
|
||||
var foo = "in";
|
||||
}
|
||||
exports.baz1 = baz1;
|
||||
// error
|
||||
function baz2(func) {
|
||||
if (func === void 0) { func = function () { return foo; }; }
|
||||
var foo = "in";
|
||||
}
|
||||
exports.baz2 = baz2;
|
||||
// error
|
||||
function baz3(func) {
|
||||
if (func === void 0) { func = (function () {
|
||||
function class_1() {
|
||||
this.x = foo;
|
||||
}
|
||||
return class_1;
|
||||
}()); }
|
||||
var foo = "in";
|
||||
}
|
||||
exports.baz3 = baz3;
|
15
tests/cases/compiler/capturedParametersInInitializers1.ts
Normal file
15
tests/cases/compiler/capturedParametersInInitializers1.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
// ok - usage is deferred
|
||||
function foo1(y = class {c = x}, x = 1) {
|
||||
new y().c;
|
||||
}
|
||||
|
||||
// ok - used in file
|
||||
function foo2(y = function(x: typeof z) {}, z = 1) {
|
||||
|
||||
}
|
||||
|
||||
// ok -used in type
|
||||
let a;
|
||||
function foo3(y = { x: <typeof z>a }, z = 1) {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
function foo(y = class {static c = x}, x = 1) {
|
||||
y.c
|
||||
}
|
||||
function foo2(y = class {[x] = x}, x = 1) {
|
||||
}
|
18
tests/cases/compiler/functionLikeInParameterInitializer.ts
Normal file
18
tests/cases/compiler/functionLikeInParameterInitializer.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// error
|
||||
export function bar(func = () => foo) {
|
||||
let foo = "in";
|
||||
}
|
||||
// error
|
||||
export function baz1(func = { f() { return foo } }) {
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz2(func = function () { return foo }) {
|
||||
let foo = "in";
|
||||
}
|
||||
|
||||
// error
|
||||
export function baz3(func = class { x = foo }) {
|
||||
let foo = "in";
|
||||
}
|
Loading…
Reference in a new issue