/*
* Copyright 2015 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;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
/**
* @author johnlenz@google.com (John Lenz)
*/
public final class CheckUnusedPrivatePropertiesTest extends TypeICompilerTestCase {
private static final String EXTERNS = LINE_JOINER.join(
DEFAULT_EXTERNS,
"/** @const */ var goog = {};",
"/** @const */ goog.reflect = {};",
"goog.reflect.object;",
"/** @constructor */",
"function Window() {}",
"Window.prototype.x;",
"Window.prototype.a;",
"Window.prototype.ext;",
"/** @type !Window */ var window;",
"function alert(a) {}",
"var EXT = {};",
"EXT.ext;");
public CheckUnusedPrivatePropertiesTest() {
super(EXTERNS);
enableGatherExternProperties();
setAcceptedLanguage(LanguageMode.ECMASCRIPT_2015);
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new CheckUnusedPrivateProperties(compiler);
}
@Override
public CompilerOptions getOptions() {
CompilerOptions options = super.getOptions();
options.setWarningLevel(DiagnosticGroups.ANALYZER_CHECKS, CheckLevel.WARNING);
options.setWarningLevel(DiagnosticGroups.MISSING_PROPERTIES, CheckLevel.OFF);
// Global this is used deliberately to refer to Window in these tests
options.setWarningLevel(new DiagnosticGroup(NewTypeInference.GLOBAL_THIS), CheckLevel.OFF);
return options;
}
private void unused(String code) {
testSame(code, CheckUnusedPrivateProperties.UNUSED_PRIVATE_PROPERTY);
}
private void used(String code) {
testSame(code);
}
public void testSimpleUnused1() {
unused("/** @private */ this.a = 2");
used("/** @private */ this.a = 2; alert(this.a);");
}
public void testConstructorPropertyUsed1() {
unused("/** @constructor */ function C() {} /** @private */ C.prop = 1;");
used("/** @constructor */ function C() {} /** @private */ C.prop = 1; alert(C.prop);");
used("/** @constructor */ function C() {} /** @private */ C.prop = 1; function f(a) {a.prop}");
}
public void testClassPropUnused1() {
this.mode = TypeInferenceMode.NEITHER;
// A property defined on "this" can be removed
unused("class C { constructor() { /** @private */ this.a = 2 } }");
}
public void testClassPropUnused2() {
unused("/** @constructor */ function C() { /** @private */ this.prop = 1; }");
used("/** @constructor */ function C() {/** @private */ this.prop = 1;} alert(some.prop);");
}
public void testClassMethodUnused1() {
this.mode = TypeInferenceMode.NEITHER;
unused("class C { constructor() {} /** @private */ method() {} }");
used("class C { constructor() {} /** @private */ method() {} }\n new C().method();");
}
// The JSDoc seems to be missing here, reenable this test when it is fixed.
public void disable_testClassMethodUnused2() {
this.mode = TypeInferenceMode.NEITHER;
unused("class C { constructor() {}\n /** @private */ ['method']() {} }");
used("class C { constructor() {}\n /** @private */ ['method']() {} }\n new C()['method']();");
}
public void testSimple2() {
// A property defined on "this" can be removed, even when defined
// as part of an expression
unused("/** @private */ this.a = 1; this.a = 2, f()");
unused("/** @private */ this.a = 1; x = (this.a = 2, f())");
unused("/** @private */ this.a = 1; x = (f(), this.a = 2)");
}
public void testSimple3() {
// A property defined on an object other than "this" can not be removed.
used("y.a = 2");
// and is seen as a use.
used("y.a = 2; /** @private */ this.a = 2");
// Some use of the property "a" appears as a use.
used("y.a = 2; /** @private */ this.a = 1; alert(x.a)");
}
public void testObjLit() {
// A property defined on an object other than "this" is considered a use.
used("({a:2})");
// and is seen as a use on 'this'.
used("({a:0}); /** @private */ this.a = 1;");
// Some use of the property "a" anywhere is considered a use
used("x = ({a:0}); /** @private */ this.a = 1; alert(x.a)");
}
public void testExtern() {
// A property defined in the externs and isn't a warning.
testSame("this.ext = 2");
}
public void testExport() {
// An exported property can not be removed.
testSame("this.ext = 2; window['export'] = this.ext;");
testSame("function f() { this.ext = 2; } window['export'] = this.ext;");
}
public void testAssignOp1() {
// Properties defined using a compound assignment can be removed if the
// result of the assignment expression is not immediately used.
unused("/** @private */ this.x; this.x += 2");
used("/** @private */ this.x; x = (this.x += 2)");
used("/** @private */ this.x; this.x += 2; x = this.x;");
// But, of course, a later use prevents its removal.
used("/** @private */ this.x; this.x += 2; x.x;");
}
public void testAssignOp2() {
// Properties defined using a compound assignment can be removed if the
// result of the assignment expression is not immediately used.
unused("/** @private */ this.a; this.a += 2, f()");
unused("/** @private */ this.a; x = (this.a += 2, f())");
used("/** @private */ this.a; x = (f(), this.a += 2)");
}
public void testInc1() {
// Increments and Decrements are handled similarly to compound assignments
// but need a placeholder value when replaced.
unused("/** @private */ this.x; this.x++");
used("/** @private */ this.x; x = (this.x++)");
used("/** @private */ this.x; this.x++; x = this.x;");
unused("/** @private */ this.x; --this.x");
used("/** @private */ this.x; x = (--this.x)");
used("/** @private */ this.x; --this.x; x = this.x;");
}
public void testInc2() {
// Increments and Decrements are handled similarly to compound assignments
unused("/** @private */ this.a; this.a++, f()");
unused("/** @private */ this.a;x = (this.a++, f())");
used("/** @private */ this.a;x = (f(), this.a++)");
unused("/** @private */ this.a; --this.a, f()");
unused("/** @private */ this.a; x = (--this.a, f())");
used("/** @private */ this.a; x = (f(), --this.a)");
}
public void testJSCompiler_renameProperty() {
// JSCompiler_renameProperty introduces a use of the property
used("/** @private */ this.a = 2; x[JSCompiler_renameProperty('a')]");
used("/** @private */ this.a = 2; JSCompiler_renameProperty('a')");
}
public void testForIn() {
// This is the basic assumption that this pass makes:
// that it can warn even if it is used indirectly in a for-in loop
unused(LINE_JOINER.join(
"/** @constructor */ var X = function() {",
" /** @private */ this.y = 1;",
"}",
"for (var a in new X()) { alert(x[a]) }"));
}
public void testObjectReflection1() {
// Verify reflection prevents warning.
used(LINE_JOINER.join(
"/** @constructor */ function A() {/** @private */ this.foo = 1;}",
"use(goog.reflect.object(A, {foo: 'foo'}));"));
}
public void testObjectReflection2() {
// Any object literal definition prevents warning.
used(LINE_JOINER.join(
"/** @constructor */ function A() {/** @private */ this.foo = 1;}",
"use({foo: 'foo'});"));
}
public void testPrototypeProps1() {
unused(LINE_JOINER.join(
"/** @constructor */ function A() {this.foo = 1;}",
"/** @private */ A.prototype.foo = 0;",
"A.prototype.method = function() {this.foo++};",
"new A().method()"));
}
public void testPrototypeProps2() {
// don't warn about properties that are exported by convention
used(LINE_JOINER.join(
"/** @constructor */ function A() {this._foo = 1;}",
"/** @private */ A.prototype._foo = 0;",
"A.prototype.method = function() {this._foo++};",
"new A().method()"));
}
public void testTypedef() {
used(LINE_JOINER.join(
"/** @constructor */ function A() {}",
"/** @private @typedef {string} */ A.typedef_;"));
}
public void testInterface() {
used(LINE_JOINER.join(
"/** @constructor */ function A() {}",
"/**",
" * @interface",
" * @private",
" */",
"A.Interface = function() {};"));
}
public void testConstructorProperty1() {
unused("/** @constructor */ function C() {} /** @private */ C.prop = 1;");
}
public void testConstructorProperty2() {
used(
"/** @constructor */ function C() {} "
+ "/** @private */ C.prop = 1; "
+ "function use(a) { alert(a.prop) }; "
+ "use(C)");
}
}