From 0bc0330dc25050e8119c2ffa280a8d8188f37b84 Mon Sep 17 00:00:00 2001 From: tbreisacher Date: Thu, 3 Nov 2016 16:52:26 -0700 Subject: [PATCH] Check @implements annotations on classes as well as @constructor-annotated functions. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=138136196 --- .../jscomp/CheckRequiresForConstructors.java | 10 ++-- .../javascript/jscomp/MissingRequireTest.java | 50 +++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/com/google/javascript/jscomp/CheckRequiresForConstructors.java b/src/com/google/javascript/jscomp/CheckRequiresForConstructors.java index f42f99d65d4..f1d45c0f919 100644 --- a/src/com/google/javascript/jscomp/CheckRequiresForConstructors.java +++ b/src/com/google/javascript/jscomp/CheckRequiresForConstructors.java @@ -578,18 +578,18 @@ private void maybeAddGoogScopeUsage(NodeTraversal t, Node n, Node parent) { *
  • var foo = function() {};
    *
  • foo.bar = function() {};
    */ - private boolean declaresFunction(Node n) { - if (n.isFunction()) { + private boolean declaresFunctionOrClass(Node n) { + if (n.isFunction() || n.isClass()) { return true; } - if (n.isAssign() && n.getLastChild().isFunction()) { + if (n.isAssign() && (n.getLastChild().isFunction() || n.getLastChild().isClass())) { return true; } if (NodeUtil.isNameDeclaration(n) && n.getFirstChild().hasChildren() - && n.getFirstFirstChild().isFunction()) { + && (n.getFirstFirstChild().isFunction() || n.getFirstFirstChild().isClass())) { return true; } @@ -602,7 +602,7 @@ private void maybeAddJsDocUsages(NodeTraversal t, Node n) { return; } - if (declaresFunction(n)) { + if (declaresFunctionOrClass(n)) { for (JSTypeExpression expr : info.getImplementedInterfaces()) { maybeAddUsage(t, n, expr); } diff --git a/test/com/google/javascript/jscomp/MissingRequireTest.java b/test/com/google/javascript/jscomp/MissingRequireTest.java index dcc3ec2d74a..214e8e2ec30 100644 --- a/test/com/google/javascript/jscomp/MissingRequireTest.java +++ b/test/com/google/javascript/jscomp/MissingRequireTest.java @@ -448,6 +448,56 @@ public void testFailWithImplements() { testMissingRequire(js, warning); } + public void testFailWithImplements_class() { + setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); + + String[] js = new String[] { + "var goog = {};" + + "goog.provide('example.Foo'); /** @interface */ example.Foo = function() {};", + + "/** @implements {example.Foo} */ var SomeClass = class {};" + }; + String warning = "missing require: 'example.Foo'"; + testMissingRequire(js, warning); + } + + public void testFailWithImplements_class2() { + setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); + + String[] js = new String[] { + "var goog = {};" + + "goog.provide('example.Foo'); /** @interface */ example.Foo = function() {};", + + "goog.provide('example.Bar'); /** @implements {example.Foo} */ example.Bar = class {};" + }; + String warning = "missing require: 'example.Foo'"; + testMissingRequire(js, warning); + } + + public void testFailWithImplements_googModule() { + String[] js = new String[] { + "goog.provide('example.Interface'); /** @interface */ example.Interface = function() {};", + + "goog.module('foo.Bar');" + + "/** @constructor @implements {example.Interface} */ function Bar() {}; exports = Bar;" + }; + String warning = "missing require: 'example.Interface'"; + testMissingRequire(js, warning); + } + + public void testFailWithImplements_class_googModule() { + setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT); + + String[] js = new String[] { + "goog.provide('example.Interface'); /** @interface */ example.Interface = function() {};", + + "goog.module('foo.Bar');" + + "/** @implements {example.Interface} */ class Bar {}; exports = Bar;" + }; + String warning = "missing require: 'example.Interface'"; + testMissingRequire(js, warning); + } + public void testInterfaceExtends() { String js = LINE_JOINER.join(