Skip to content

Commit

Permalink
Tighten strict missing properties for type "Object"
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=184187706
  • Loading branch information
concavelenz authored and lauraharker committed Feb 1, 2018
1 parent e9152d8 commit 12331f1
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 84 deletions.
103 changes: 54 additions & 49 deletions src/com/google/javascript/jscomp/TypeCheck.java
Expand Up @@ -1540,57 +1540,62 @@ private boolean isQNameAssignmentTarget(Node n) {
private void checkPropertyAccessHelper(
JSType objectType, String propName, NodeTraversal t, Node n) {
boolean isStruct = objectType.isStruct();
if (!reportMissingProperties
|| objectType.isEmptyType()
|| (!isStruct && allowLoosePropertyAccessOnNode(n))) {
return;
}
PropDefinitionKind kind = typeRegistry.canPropertyBeDefined(objectType, propName);
if (kind.equals(PropDefinitionKind.KNOWN)) {
return;
}
// If the property definition is known, but only loosely associated,
// only report a "strict error" which can be optional as code is migrated.
boolean isLooselyAssociated = kind.equals(PropDefinitionKind.LOOSE);
boolean isUnknownType = objectType.isUnknownType();
if (isLooselyAssociated && isUnknownType) {
// We still don't want to report this.
return;
}
boolean isObjectType = objectType.isEquivalentTo(getNativeType(OBJECT_TYPE));
boolean lowConfidence = isUnknownType || isObjectType;
boolean loosePropertyDeclaration = isQNameAssignmentTarget(n) && !isStruct;
if (!objectType.isEmptyType() && reportMissingProperties
&& (!allowLoosePropertyAccessOnNode(n) || isStruct)) {
PropDefinitionKind kind = typeRegistry.canPropertyBeDefined(objectType, propName);
if (!kind.equals(PropDefinitionKind.KNOWN)) {
// If the property definition is known, but only loosely associated,
// only report a "strict error" which can be optional as code is migrated.
boolean strict = kind.equals(PropDefinitionKind.LOOSE) || loosePropertyDeclaration;
boolean lowConfidence =
objectType.isUnknownType() || objectType.isEquivalentTo(getNativeType(OBJECT_TYPE));
SuggestionPair pair = null;
if (lowConfidence) {
if (strict) {
return;
}
} else {
pair = getClosestPropertySuggestion(objectType, propName);
// Traditionally, we would not report a warning for "loose" properties, but we want to be
// able to be more strict, so introduce an optional warning.
}
if (pair != null && pair.distance * 4 < propName.length()) {
DiagnosticType reportType;
if (strict) {
reportType = STRICT_INEXISTENT_PROPERTY_WITH_SUGGESTION;
} else {
reportType = INEXISTENT_PROPERTY_WITH_SUGGESTION;
}
report(
t,
n.getLastChild(),
reportType,
propName,
typeRegistry.getReadableTypeName(n.getFirstChild()),
pair.suggestion);
} else {
DiagnosticType reportType;
if (lowConfidence) {
reportType = POSSIBLE_INEXISTENT_PROPERTY;
} else if (strict) {
reportType = STRICT_INEXISTENT_PROPERTY;
} else {
reportType = INEXISTENT_PROPERTY;
}
report(
t,
n.getLastChild(),
reportType,
propName,
typeRegistry.getReadableTypeName(n.getFirstChild()));
}
// Traditionally, we would not report a warning for "loose" properties, but we want to be
// able to be more strict, so introduce an optional warning.
boolean strictReport = isLooselyAssociated || loosePropertyDeclaration;
SuggestionPair pair = null;
if (!lowConfidence) {
pair = getClosestPropertySuggestion(objectType, propName);
}
if (pair != null && pair.distance * 4 < propName.length()) {
DiagnosticType reportType;
if (strictReport) {
reportType = STRICT_INEXISTENT_PROPERTY_WITH_SUGGESTION;
} else {
reportType = INEXISTENT_PROPERTY_WITH_SUGGESTION;
}
report(
t,
n.getLastChild(),
reportType,
propName,
typeRegistry.getReadableTypeName(n.getFirstChild()),
pair.suggestion);
} else {
DiagnosticType reportType;
if (strictReport) {
reportType = STRICT_INEXISTENT_PROPERTY;
} else if (lowConfidence) {
reportType = POSSIBLE_INEXISTENT_PROPERTY;
} else {
reportType = INEXISTENT_PROPERTY;
}
report(
t,
n.getLastChild(),
reportType,
propName,
typeRegistry.getReadableTypeName(n.getFirstChild()));
}
}

Expand Down
155 changes: 120 additions & 35 deletions test/com/google/javascript/jscomp/TypeCheckTest.java
Expand Up @@ -2257,16 +2257,37 @@ public void testFunctionInference22() {
"/** @param {boolean} x */ var g = function(x) {};");
}

public void testFunctionInference23() {
public void testFunctionInference23a() {
// We want to make sure that 'prop' isn't declared on all objects.

// This test is specifically checking loose property check behavior.
compiler.getOptions().setWarningLevel(
DiagnosticGroups.STRICT_MISSING_PROPERTIES, CheckLevel.OFF);

testTypes(
"/** @type {!Function} */ var f = function() {\n" +
" /** @type {number} */ this.prop = 3;\n" +
"};" +
"/**\n" +
" * @param {Object} x\n" +
" * @return {string}\n" +
" */ var g = function(x) { return x.prop; };");
lines(
"/** @type {!Function} */ var f = function() {",
" /** @type {number} */ this.prop = 3;",
"};",
"/**",
" * @param {Object} x",
" * @return {string}",
" */ var g = function(x) { return x.prop; };"));
}

public void testFunctionInference23b() {
// We want to make sure that 'prop' isn't declared on all objects.

testTypes(
lines(
"/** @type {!Function} */ var f = function() {",
" /** @type {number} */ this.prop = 3;",
"};",
"/**",
" * @param {Object} x",
" * @return {string}",
" */ var g = function(x) { return x.prop; };"),
"Property prop never defined on Object");
}

public void testInnerFunction1() {
Expand Down Expand Up @@ -9206,6 +9227,11 @@ public void testCast15() {
// while because of the cast, it could have any type (including
// a union).
compiler.getOptions().setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015);

// This test is specifically checking loose property check behavior.
compiler.getOptions().setWarningLevel(
DiagnosticGroups.STRICT_MISSING_PROPERTIES, CheckLevel.OFF);

testTypes(
"for (var i = 0; i < 10; i++) {" +
"var x = /** @type {Object|number} */ ({foo: 3});" +
Expand All @@ -9216,6 +9242,26 @@ public void testCast15() {
"Property foo never defined on Array");
}

public void testCast15b() {
// This fixes a bug where a type cast on an object literal
// would cause a run-time cast exception if the node was visited
// more than once.
//
// Some code assumes that an object literal must have a object type,
// while because of the cast, it could have any type (including
// a union).
compiler.getOptions().setLanguageIn(CompilerOptions.LanguageMode.ECMASCRIPT_2015);
testTypes(
lines(
"for (var i = 0; i < 10; i++) {",
"var x = /** @type {{foo:number}}|number} */ ({foo: 3});",
"/** @param {number} x */ function f(x) {}",
"f(x.foo);",
"f([].foo);",
"}"),
"Property foo never defined on Array");
}

public void testCast16() {
// A type cast should not invalidate the checks on the members
testTypes(
Expand Down Expand Up @@ -9808,17 +9854,33 @@ public void testUnknownConstructorInstanceType3() {
testTypes("function g(f) { var x = new f(); x.a = 1; return x; }");
}

public void testUnknownPrototypeChain() {
testTypes("/**\n" +
"* @param {Object} co\n" +
" * @return {Object}\n" +
" */\n" +
"function inst(co) {\n" +
" /** @constructor */\n" +
" var c = function() {};\n" +
" c.prototype = co.prototype;\n" +
" return new c;\n" +
"}");
public void testUnknownPrototypeChain1() {
testTypes(
lines("/**",
"* @param {Object} co",
" * @return {Object}",
" */",
"function inst(co) {",
" /** @constructor */",
" var c = function() {};",
" c.prototype = co.prototype;",
" return new c;",
"}"),
"Property prototype never defined on Object");
}

public void testUnknownPrototypeChain2() {
testTypes(
lines("/**",
" * @param {Function} co",
" * @return {Object}",
" */",
"function inst(co) {",
" /** @constructor */",
" var c = function() {};",
" c.prototype = co.prototype;",
" return new c;",
"}"));
}

public void testNamespacedConstructor() {
Expand Down Expand Up @@ -12003,7 +12065,8 @@ public void testMissingProperty30() {
" return {};" +
"}" +
"f().a = 3;" +
"/** @param {Object} y */ function g(y) { return y.a; }");
"/** @param {Object} y */ function g(y) { return y.a; }",
"Property a never defined on Object");
}

public void testMissingProperty31a() {
Expand Down Expand Up @@ -13594,10 +13657,15 @@ public void testInterfaceExtendsResolution() {
}

public void testPropertyCanBeDefinedInObject() {
testTypes("/** @interface */ function I() {};" +
"I.prototype.bar = function() {};" +
"/** @type {Object} */ var foo;" +
"foo.bar();");
// This test is specifically checking loose property check behavior.
compiler.getOptions().setWarningLevel(
DiagnosticGroups.STRICT_MISSING_PROPERTIES, CheckLevel.OFF);
testTypes(
lines(
"/** @interface */ function I() {};",
"I.prototype.bar = function() {};",
"/** @type {Object} */ var foo;",
"foo.bar();"));
}

private void checkObjectType(ObjectType objectType, String propertyName,
Expand Down Expand Up @@ -14368,17 +14436,21 @@ public void testNonexistentPropertyAccessStructSubtype2() {
}

public void testIssue1024a() {
testTypes(
"/** @param {Object} a */\n" +
"function f(a) {\n" +
" a.prototype = '__proto'\n" +
"}\n" +
"/** @param {Object} b\n" +
" * @return {!Object}\n" +
" */\n" +
"function g(b) {\n" +
" return b.prototype\n" +
"}\n");
// This test is specifically checking loose property check behavior.
compiler.getOptions().setWarningLevel(
DiagnosticGroups.STRICT_MISSING_PROPERTIES, CheckLevel.OFF);
testTypes(
lines(
"/** @param {Object} a */",
"function f(a) {",
" a.prototype = '__proto'",
"}",
"/** @param {Object} b",
" * @return {!Object}",
" */",
"function g(b) {",
" return b.prototype",
"}"));
}

public void testIssue1024b() {
Expand Down Expand Up @@ -18165,6 +18237,19 @@ public void testb38182645() {
"required: null"));
}

public void testMissingPropertiesWarningOnObject1() {
testTypes(lines(
"/** @constructor */",
"function Foo() {",
" this.prop = 123;",
"}",
"/** @param {!Object} x */",
"function f(x) {",
" return x.prop;",
"}"),
"Property prop never defined on Object");
}

private void testTypes(String js) {
testTypes(js, (String) null);
}
Expand Down

0 comments on commit 12331f1

Please sign in to comment.