/*
* Copyright 2012 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
/** @author johnlenz@google.com (John Lenz) */
public final class InlinePropertiesTest extends TypeICompilerTestCase {
private static final String EXTERNS =
LINE_JOINER.join(
MINIMAL_EXTERNS,
"Function.prototype.call=function(){};",
"Function.prototype.inherits=function(){};",
"prop.toString;",
"var google = { gears: { factory: {}, workerPool: {} } };",
"/** @type {?} */ var externUnknownVar;",
"/** @type {!Function} */ var externFn;");
public InlinePropertiesTest() {
super(EXTERNS);
enableNormalize();
enableClosurePass();
enableGatherExternProperties();
this.mode = TypeInferenceMode.BOTH;
}
@Override
protected CompilerOptions getOptions() {
CompilerOptions options = super.getOptions();
// Ignore a few type warnings: we intentionally trigger these warnings
// to make sure that the pass still operates correctly with bad code.
DiagnosticGroup ignored =
new DiagnosticGroup(
TypeCheck.INEXISTENT_PROPERTY,
NewTypeInference.GLOBAL_THIS,
NewTypeInference.INEXISTENT_PROPERTY,
NewTypeInference.INVALID_ARGUMENT_TYPE,
TypeValidator.TYPE_MISMATCH_WARNING);
options.setWarningLevel(ignored, CheckLevel.OFF);
return options;
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new InlineProperties(compiler);
}
@Override
protected int getNumRepetitions() {
return 1;
}
public void testConstInstanceProp1() {
// Replace a reference to known constant property.
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"new C().foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"new C(), 1;"));
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" {",
" this.foo = 1;",
" }",
"}",
"new C().foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" {",
" this.foo = 1;",
" }",
"}",
"new C(), 1;"));
}
public void testConstInstanceProp2() {
// Replace a constant reference
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"var x = new C();",
"x.foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1",
"}",
"var x = new C();",
"1;\n"));
}
public void testConstInstanceProp3() {
// Replace a constant reference
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"/** @type {C} */",
"var x = new C();",
"x.foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1",
"}",
"/** @type {C} */",
"var x = new C();",
"1;\n"));
}
public void testConstInstanceProp4() {
// This pass replies on DisambiguateProperties to distinguish like named
// properties so it doesn't handle this case.
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"/** @constructor */",
"function B() {",
" this.foo = 1;",
"}",
"new C().foo;\n"));
}
public void testConstClassProps1() {
// Inline constant class properties,
test(
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
"}",
"C.bar = 2;",
"C.foo = 1;",
"var z = C.foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
"}",
"C.bar = 2;",
"C.foo = 1;",
"var z = 1;"));
}
public void testConstClassProps2() {
// Don't confuse, class properties with instance properties
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"var z = C.foo;"));
}
public void testConstClassProps3() {
// Don't confuse, class properties with prototype properties
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"var z = C.foo;\n"));
}
public void testConstClassProps4() {
// Don't confuse unique constructors with similiar function types
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function D() {}",
"/** @type {function(new:C): undefined} */",
"var x = D;",
"/** @type {number} */ x.foo = 1;",
"var z = C.foo;\n"));
}
public void testConstClassProps5() {
// Don't confuse subtype constructors properties
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"/** @constructor @extends {C} */",
"function D() {}",
"D.foo = 1;",
"var z = C.foo;\n"));
}
public void testConstClassProps6() {
// Don't inline to unknowns
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.foo = 1;",
"var z = externUnknownVar.foo;\n"));
}
public void testConstClassProps7() {
// Don't inline to Function prop
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.foo = 1;",
"var z = externFn.foo;\n"));
}
public void testNonConstClassProp1() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
"}",
"C.foo = 1;",
"alert(C.foo);",
"delete C.foo;"));
}
public void testNonConstClassProp2() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
"}",
"C.foo = 1;",
"alert(C.foo);",
"C.foo = 2;"));
}
public void testNonConstClassProp3() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
"}",
"C.foo = 1;",
"function f(a) {",
" a.foo = 2;",
"}",
"alert(C.foo);",
"f(C);"));
}
public void testNonConstInstanceProp1() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"var x = new C();",
"alert(x.foo);",
"delete x.foo;"));
}
public void testNonConstInstanceProp2() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" this.foo = 1;",
"}",
"var x = new C();",
"alert(x.foo);",
"x.foo = 2;"));
}
public void testNonConstructorInstanceProp1() {
testSame(LINE_JOINER.join(
"function C() {",
" this.foo = 1;",
" return this;",
"}",
"C().foo;"));
}
public void testConditionalInstanceProp1() {
testSame(LINE_JOINER.join(
"/** @constructor */",
"function C() {",
" if (false) this.foo = 1;",
"}",
"new C().foo;"));
}
public void testConstPrototypeProp1() {
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"new C().foo;\n"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"new C(), 1;\n"));
}
public void testConstPrototypeProp2() {
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"var x = new C();",
"x.foo;\n"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"var x = new C();",
"1;\n"));
}
public void testConstPrototypePropInGlobalBlockScope() {
test(LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"{",
" C.prototype.foo = 1;",
"}",
"var x = new C();",
"x.foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"{",
" C.prototype.foo = 1;",
"}",
"var x = new C();",
"1;"));
}
public void testGlobalThisNotInlined() {
testSame(LINE_JOINER.join(
"this.foo = 1;",
"/** @constructor */",
"function C() {",
" foo;",
"}"));
}
public void testConstPrototypePropFromSuper() {
test(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"/** @constructor @extends {C} */",
"function D() {}",
"(new D).foo;"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"/** @constructor @extends {C} */",
"function D() {}",
"new D, 1;"));
}
public void testTypedPropInlining() {
test(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"function f(/** !C */ x) { return x.foo; }",
"f(new C);"),
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"function f(/** !C */ x) { return 1; }",
"f(new C);"));
}
public void testTypeMismatchNoPropInlining() {
testSame(
LINE_JOINER.join(
"/** @constructor */",
"function C() {}",
"C.prototype.foo = 1;",
"function f(/** !C */ x) { return x.foo; }",
"f([]);"));
}
public void testStructuralInterfacesNoPropInlining() {
testSame(
LINE_JOINER.join(
"/** @record */ function I() {}",
"/** @type {number|undefined} */ I.prototype.foo;",
"",
"/** @constructor @implements {I} */",
"function C() {}",
"/** @override */",
"C.prototype.foo = 1;",
"",
"function f(/** !I */ x) { return x.foo; }",
"f([]);"));
}
}