diff --git a/src/com/google/javascript/jscomp/DefaultPassConfig.java b/src/com/google/javascript/jscomp/DefaultPassConfig.java
index cae6270e27d..336de8a8aaa 100644
--- a/src/com/google/javascript/jscomp/DefaultPassConfig.java
+++ b/src/com/google/javascript/jscomp/DefaultPassConfig.java
@@ -51,10 +51,10 @@
import com.google.javascript.jscomp.lint.CheckJSDocStyle;
import com.google.javascript.jscomp.lint.CheckMissingSemicolon;
import com.google.javascript.jscomp.lint.CheckNoMutatedEs6Exports;
+import com.google.javascript.jscomp.lint.CheckNullabilityModifiers;
import com.google.javascript.jscomp.lint.CheckNullableReturn;
import com.google.javascript.jscomp.lint.CheckPrimitiveAsObject;
import com.google.javascript.jscomp.lint.CheckPrototypeProperties;
-import com.google.javascript.jscomp.lint.CheckRedundantNullabilityModifier;
import com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted;
import com.google.javascript.jscomp.lint.CheckUnusedLabels;
import com.google.javascript.jscomp.lint.CheckUselessBlocks;
@@ -1971,9 +1971,9 @@ protected HotSwapCompilerPass create(AbstractCompiler compiler) {
.add(new CheckInterfaces(compiler))
.add(new CheckJSDocStyle(compiler))
.add(new CheckMissingSemicolon(compiler))
+ .add(new CheckNullabilityModifiers(compiler))
.add(new CheckPrimitiveAsObject(compiler))
.add(new CheckPrototypeProperties(compiler))
- .add(new CheckRedundantNullabilityModifier(compiler))
.add(new CheckUnusedLabels(compiler))
.add(new CheckUselessBlocks(compiler));
return combineChecks(compiler, callbacks.build());
diff --git a/src/com/google/javascript/jscomp/DiagnosticGroups.java b/src/com/google/javascript/jscomp/DiagnosticGroups.java
index 21a97e2ba33..f092d8f313d 100644
--- a/src/com/google/javascript/jscomp/DiagnosticGroups.java
+++ b/src/com/google/javascript/jscomp/DiagnosticGroups.java
@@ -31,10 +31,10 @@
import com.google.javascript.jscomp.lint.CheckJSDocStyle;
import com.google.javascript.jscomp.lint.CheckMissingSemicolon;
import com.google.javascript.jscomp.lint.CheckNoMutatedEs6Exports;
+import com.google.javascript.jscomp.lint.CheckNullabilityModifiers;
import com.google.javascript.jscomp.lint.CheckNullableReturn;
import com.google.javascript.jscomp.lint.CheckPrimitiveAsObject;
import com.google.javascript.jscomp.lint.CheckPrototypeProperties;
-import com.google.javascript.jscomp.lint.CheckRedundantNullabilityModifier;
import com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted;
import com.google.javascript.jscomp.lint.CheckUnusedLabels;
import com.google.javascript.jscomp.lint.CheckUselessBlocks;
@@ -580,10 +580,11 @@ public DiagnosticGroup forName(String name) {
CheckInterfaces.INTERFACE_FUNCTION_NOT_EMPTY,
CheckInterfaces.INTERFACE_SHOULD_NOT_TAKE_ARGS,
CheckMissingSemicolon.MISSING_SEMICOLON,
+ CheckNullabilityModifiers.MISSING_NULLABILITY_MODIFIER_JSDOC,
+ CheckNullabilityModifiers.REDUNDANT_NULLABILITY_MODIFIER_JSDOC,
CheckPrimitiveAsObject.NEW_PRIMITIVE_OBJECT,
CheckPrimitiveAsObject.PRIMITIVE_OBJECT_DECLARATION,
CheckPrototypeProperties.ILLEGAL_PROTOTYPE_MEMBER,
- CheckRedundantNullabilityModifier.REDUNDANT_NULLABILITY_MODIFIER_JSDOC,
CheckRequiresAndProvidesSorted.DUPLICATE_REQUIRE,
CheckRequiresAndProvidesSorted.REQUIRES_NOT_SORTED,
CheckRequiresAndProvidesSorted.PROVIDES_NOT_SORTED,
@@ -659,7 +660,8 @@ public DiagnosticGroup forName(String name) {
// the file level is by using a whitelist file.
@GwtIncompatible("Conformance")
public static final DiagnosticGroup CONFORMANCE_VIOLATIONS =
- DiagnosticGroups.registerGroup("conformanceViolations",
+ DiagnosticGroups.registerGroup(
+ "conformanceViolations",
CheckConformance.CONFORMANCE_VIOLATION,
CheckConformance.CONFORMANCE_POSSIBLE_VIOLATION);
@@ -670,16 +672,14 @@ public DiagnosticGroup forName(String name) {
public static final DiagnosticGroup MISSING_POLYFILL =
DiagnosticGroups.registerGroup(
- "missingPolyfill",
- RewritePolyfills.INSUFFICIENT_OUTPUT_VERSION_ERROR);
+ "missingPolyfill", RewritePolyfills.INSUFFICIENT_OUTPUT_VERSION_ERROR);
// For internal use only, so there are no constants for these groups.
static {
- DiagnosticGroups.registerGroup("invalidProvide",
- ProcessClosurePrimitives.INVALID_PROVIDE_ERROR);
+ DiagnosticGroups.registerGroup(
+ "invalidProvide", ProcessClosurePrimitives.INVALID_PROVIDE_ERROR);
- DiagnosticGroups.registerGroup("es6Typed",
- RhinoErrorReporter.MISPLACED_TYPE_SYNTAX);
+ DiagnosticGroups.registerGroup("es6Typed", RhinoErrorReporter.MISPLACED_TYPE_SYNTAX);
DiagnosticGroups.registerDeprecatedGroup("duplicateZipContents");
}
diff --git a/src/com/google/javascript/jscomp/LintPassConfig.java b/src/com/google/javascript/jscomp/LintPassConfig.java
index 92d1389af1b..1829a919f66 100644
--- a/src/com/google/javascript/jscomp/LintPassConfig.java
+++ b/src/com/google/javascript/jscomp/LintPassConfig.java
@@ -22,9 +22,9 @@
import com.google.javascript.jscomp.lint.CheckInterfaces;
import com.google.javascript.jscomp.lint.CheckJSDocStyle;
import com.google.javascript.jscomp.lint.CheckMissingSemicolon;
+import com.google.javascript.jscomp.lint.CheckNullabilityModifiers;
import com.google.javascript.jscomp.lint.CheckPrimitiveAsObject;
import com.google.javascript.jscomp.lint.CheckPrototypeProperties;
-import com.google.javascript.jscomp.lint.CheckRedundantNullabilityModifier;
import com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted;
import com.google.javascript.jscomp.lint.CheckUnusedLabels;
import com.google.javascript.jscomp.lint.CheckUselessBlocks;
@@ -73,7 +73,7 @@ protected CompilerPass create(AbstractCompiler compiler) {
new CheckSuper(compiler),
new CheckPrimitiveAsObject(compiler),
new ClosureCheckModule(compiler),
- new CheckRedundantNullabilityModifier(compiler),
+ new CheckNullabilityModifiers(compiler),
new CheckRequiresAndProvidesSorted(compiler),
new CheckSideEffects(
compiler, /* report */ true, /* protectSideEffectFreeCode */ false),
diff --git a/src/com/google/javascript/jscomp/lint/CheckNullabilityModifiers.java b/src/com/google/javascript/jscomp/lint/CheckNullabilityModifiers.java
new file mode 100644
index 00000000000..e38db93beba
--- /dev/null
+++ b/src/com/google/javascript/jscomp/lint/CheckNullabilityModifiers.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2018 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.lint;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.javascript.jscomp.AbstractCompiler;
+import com.google.javascript.jscomp.CompilerPass;
+import com.google.javascript.jscomp.DiagnosticType;
+import com.google.javascript.jscomp.NodeTraversal;
+import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
+import com.google.javascript.jscomp.NodeUtil;
+import com.google.javascript.rhino.JSDocInfo;
+import com.google.javascript.rhino.JSTypeExpression;
+import com.google.javascript.rhino.Node;
+import com.google.javascript.rhino.Token;
+import java.util.HashSet;
+
+/**
+ * Checks for missing or redundant nullability modifiers.
+ *
+ *
Primitive and literal types should not be preceded by a `!` modifier. Reference types must be
+ * preceded by a `?` or `!` modifier.
+ */
+public class CheckNullabilityModifiers extends AbstractPostOrderCallback implements CompilerPass {
+
+ /** The diagnostic for a missing nullability modifier. */
+ public static final DiagnosticType MISSING_NULLABILITY_MODIFIER_JSDOC =
+ DiagnosticType.disabled(
+ "JSC_MISSING_NULLABILITY_MODIFIER_JSDOC",
+ "{0} is a reference type with no nullability modifier, "
+ + "which is disallowed by the style guide.\n"
+ + "Please add a '!' to make it explicitly non-nullable, "
+ + "or a '?' to make it explicitly nullable.");
+
+ /** The diagnostic for a redundant nullability modifier. */
+ public static final DiagnosticType REDUNDANT_NULLABILITY_MODIFIER_JSDOC =
+ DiagnosticType.disabled(
+ "JSC_REDUNDANT_NULLABILITY_MODIFIER_JSDOC",
+ "{0} is a non-reference type which is already non-nullable.\n"
+ + "Please remove the redundant '!', which is disallowed by the style guide.");
+
+ /** The set of primitive type names. Note that `void` is a synonym for `undefined`. */
+ private static final ImmutableSet PRIMITIVE_TYPE_NAMES =
+ ImmutableSet.of("boolean", "number", "string", "symbol", "undefined", "void", "null");
+
+ private final AbstractCompiler compiler;
+
+ // Store the candidate warnings and template types found while traversing a single script node.
+ private final HashSet redundantCandidates = new HashSet<>();
+ private final HashSet missingCandidates = new HashSet<>();
+ private final HashSet templateTypeNames = new HashSet<>();
+
+ public CheckNullabilityModifiers(AbstractCompiler compiler) {
+ this.compiler = compiler;
+ }
+
+ @Override
+ public void process(Node externs, Node root) {
+ NodeTraversal.traverseRoots(compiler, this, externs, root);
+ }
+
+ @Override
+ public void visit(NodeTraversal t, Node n, Node parent) {
+ JSDocInfo info = n.getJSDocInfo();
+ if (info != null) {
+ for (String typeName : info.getTemplateTypeNames()) {
+ templateTypeNames.add(typeName);
+ }
+ if (info.hasType()) {
+ visitTypeExpression(info.getType(), false);
+ }
+ for (String param : info.getParameterNames()) {
+ if (info.hasParameterType(param)) {
+ visitTypeExpression(info.getParameterType(param), false);
+ }
+ }
+ if (info.hasReturnType()) {
+ visitTypeExpression(info.getReturnType(), false);
+ }
+ if (info.hasEnumParameterType()) {
+ // JSDocInfoParser wraps the @enum type in an artificial '!' if it's not a primitive type.
+ // Thus a missing modifier warning can never trigger, but a redundant modifier warning can.
+ visitTypeExpression(info.getEnumParameterType(), false);
+ }
+ if (info.hasTypedefType()) {
+ visitTypeExpression(info.getTypedefType(), false);
+ }
+ if (info.hasThisType()) {
+ // JSDocInfoParser wraps the @this type in an artificial '!'. Thus a missing modifier
+ // warning can never trigger, but a top-level '!' should be ignored if it precedes a
+ // primitive or literal type; otherwise it will trigger a spurious redundant modifier
+ // warning.
+ visitTypeExpression(info.getThisType(), true);
+ }
+ for (JSTypeExpression expr : info.getThrownTypes()) {
+ visitTypeExpression(expr, false);
+ }
+ // JSDocInfoParser enforces the @extends and @implements types to be unqualified in the source
+ // code, so we don't need to check them.
+ }
+
+ if (n.isScript()) {
+ // If exiting a script node, report applicable warnings and clear the maps.
+ report(t);
+ redundantCandidates.clear();
+ missingCandidates.clear();
+ templateTypeNames.clear();
+ return;
+ }
+ }
+
+ private void report(NodeTraversal t) {
+ for (Node n : missingCandidates) {
+ if (shouldReport(n)) {
+ t.report(n, MISSING_NULLABILITY_MODIFIER_JSDOC, getReportedTypeName(n));
+ }
+ }
+ for (Node n : redundantCandidates) {
+ if (shouldReport(n)) {
+ // Report on parent so that modifier is also highlighted.
+ t.report(n.getParent(), REDUNDANT_NULLABILITY_MODIFIER_JSDOC, getReportedTypeName(n));
+ }
+ }
+ }
+
+ private boolean shouldReport(Node n) {
+ // Ignore type names that appear in @template clauses in the same source file. In rare cases,
+ // this may cause a false negative (if the name is used in a non-template capacity in the same
+ // file) or a false positive (if the name is used in a template capacity in a separate file),
+ // but it makes this check possible in the absence of type information. If the style guide ever
+ // mandates template types (and nothing else) to be all-caps, we can use that assumption to make
+ // this check more precise.
+ return !n.isString() || !templateTypeNames.contains(n.getString());
+ }
+
+ private void visitTypeExpression(JSTypeExpression expr, boolean hasArtificialTopLevelBang) {
+ Node root = expr.getRoot();
+ NodeUtil.visitPreOrder(
+ root,
+ (Node node) -> {
+ Node parent = node.getParent();
+
+ boolean isPrimitiveOrLiteral =
+ isPrimitiveType(node) || isFunctionLiteral(node) || isRecordLiteral(node);
+ boolean isReference = isReferenceType(node);
+
+ // Whether the node is preceded by a '!' or '?'.
+ boolean hasBang = parent != null && parent.getToken() == Token.BANG;
+ boolean hasQmark = parent != null && parent.getToken() == Token.QMARK;
+
+ // Whether the node is preceded by a '!' that wasn't artificially added.
+ boolean hasNonArtificialBang = hasBang && !(hasArtificialTopLevelBang && parent == root);
+
+ // Whether the node is the type in function(new:T) or function(this:T).
+ boolean isNewOrThis = parent != null && (parent.isNew() || parent.isThis());
+
+ if (isReference && !hasBang && !hasQmark && !isNewOrThis) {
+ missingCandidates.add(node);
+ } else if (isPrimitiveOrLiteral && hasNonArtificialBang) {
+ redundantCandidates.add(node);
+ }
+ });
+ }
+
+ private static boolean isPrimitiveType(Node node) {
+ return node.isString() && PRIMITIVE_TYPE_NAMES.contains(node.getString());
+ }
+
+ private static boolean isReferenceType(Node node) {
+ return node.isString() && !PRIMITIVE_TYPE_NAMES.contains(node.getString());
+ }
+
+ private static boolean isFunctionLiteral(Node node) {
+ return node.isFunction();
+ }
+
+ private static boolean isRecordLiteral(Node node) {
+ return node.getToken() == Token.LC;
+ }
+
+ private static String getReportedTypeName(Node node) {
+ if (isFunctionLiteral(node)) {
+ return "Function";
+ }
+ if (isRecordLiteral(node)) {
+ return "Record literal";
+ }
+ Preconditions.checkState(isPrimitiveType(node) || isReferenceType(node));
+ return node.getString();
+ }
+}
diff --git a/src/com/google/javascript/jscomp/lint/CheckRedundantNullabilityModifier.java b/src/com/google/javascript/jscomp/lint/CheckRedundantNullabilityModifier.java
deleted file mode 100644
index 0f2b77d1942..00000000000
--- a/src/com/google/javascript/jscomp/lint/CheckRedundantNullabilityModifier.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2018 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.lint;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.javascript.jscomp.AbstractCompiler;
-import com.google.javascript.jscomp.CompilerPass;
-import com.google.javascript.jscomp.DiagnosticType;
-import com.google.javascript.jscomp.NodeTraversal;
-import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
-import com.google.javascript.jscomp.NodeUtil;
-import com.google.javascript.rhino.JSDocInfo;
-import com.google.javascript.rhino.JSTypeExpression;
-import com.google.javascript.rhino.Node;
-import com.google.javascript.rhino.Token;
-
-/**
- * Checks for redundant nullability modifiers.
- *
- *
A nullability modifier '!' is redundant if it precedes a value type or a record literal.
- */
-public class CheckRedundantNullabilityModifier extends AbstractPostOrderCallback
- implements CompilerPass {
-
- /** The diagnostic for a redundant nullability modifier. */
- public static final DiagnosticType REDUNDANT_NULLABILITY_MODIFIER_JSDOC =
- DiagnosticType.warning(
- "JSC_REDUNDANT_NULLABILITY_MODIFIER_JSDOC",
- "Type is already not null. Please remove the redundant '!'.");
-
- private static final ImmutableSet PRIMITIVE_TYPE_NAMES =
- ImmutableSet.of("boolean", "number", "string", "symbol", "undefined");
-
- private final AbstractCompiler compiler;
-
- public CheckRedundantNullabilityModifier(AbstractCompiler compiler) {
- this.compiler = compiler;
- }
-
- @Override
- public void process(Node externs, Node root) {
- NodeTraversal.traverseRoots(compiler, this, externs, root);
- }
-
- @Override
- public void visit(final NodeTraversal t, final Node n, final Node p) {
- final JSDocInfo info = n.getJSDocInfo();
- if (info == null) {
- return;
- }
- if (info.hasType()) {
- checkTypeExpression(t, info.getType(), false);
- }
- for (String param : info.getParameterNames()) {
- if (info.hasParameterType(param)) {
- checkTypeExpression(t, info.getParameterType(param), false);
- }
- }
- if (info.hasReturnType()) {
- checkTypeExpression(t, info.getReturnType(), false);
- }
- if (info.hasEnumParameterType()) {
- checkTypeExpression(t, info.getEnumParameterType(), false);
- }
- if (info.hasTypedefType()) {
- checkTypeExpression(t, info.getTypedefType(), false);
- }
- // JSDocInfoParser automatically prepends a '!' to the type associated with a @this annotation.
- // It should be ignored since it doesn't actually exist in the source file.
- if (info.hasThisType()) {
- checkTypeExpression(t, info.getThisType(), true);
- }
- // We don't need to check @extends and @implements annotations since they never refer to a
- // primitive type.
- // TODO(tjgq): Should we check @throws annotations? The Google style guide forbids throwing a
- // primitive type, so the warning would be somewhat misguided.
- }
-
- private static void checkTypeExpression(
- final NodeTraversal t, JSTypeExpression expr, boolean isTopLevelBangAllowed) {
- NodeUtil.visitPreOrder(
- expr.getRoot(),
- node -> {
- Node parent = node.getParent();
- Node grandparent = parent != null ? parent.getParent() : null;
-
- boolean hasModifier = parent != null && parent.getToken() == Token.BANG;
- boolean isModifierRedundant =
- isPrimitiveType(node) || node.isFunction() || isRecordLiteral(node);
- boolean isRedundantModifierAllowed = isTopLevelBangAllowed && grandparent == null;
-
- if (hasModifier && isModifierRedundant && !isRedundantModifierAllowed) {
- t.report(parent, REDUNDANT_NULLABILITY_MODIFIER_JSDOC);
- }
- });
- }
-
- private static boolean isPrimitiveType(Node node) {
- return node.isString() && PRIMITIVE_TYPE_NAMES.contains(node.getString());
- }
-
- private static boolean isRecordLiteral(Node node) {
- return node.getToken() == Token.LC;
- }
-}
diff --git a/test/com/google/javascript/jscomp/lint/CheckNullabilityModifiersTest.java b/test/com/google/javascript/jscomp/lint/CheckNullabilityModifiersTest.java
new file mode 100644
index 00000000000..d93dc2139f1
--- /dev/null
+++ b/test/com/google/javascript/jscomp/lint/CheckNullabilityModifiersTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2018 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.lint;
+
+import com.google.javascript.jscomp.CheckLevel;
+import com.google.javascript.jscomp.Compiler;
+import com.google.javascript.jscomp.CompilerOptions;
+import com.google.javascript.jscomp.CompilerPass;
+import com.google.javascript.jscomp.CompilerTestCase;
+import com.google.javascript.jscomp.DiagnosticGroups;
+
+/** Unit tests for {@link CheckNullabilityModifiers}. */
+public final class CheckNullabilityModifiersTest extends CompilerTestCase {
+
+ @Override
+ protected CompilerPass getProcessor(Compiler compiler) {
+ return new CheckNullabilityModifiers(compiler);
+ }
+
+ @Override
+ protected CompilerOptions getOptions(CompilerOptions options) {
+ super.getOptions(options);
+ options.setWarningLevel(DiagnosticGroups.LINT_CHECKS, CheckLevel.WARNING);
+ return options;
+ }
+
+ public void testPrimitiveType() {
+ checkRedundantWarning("/** @type {!boolean} */ var x;");
+ checkRedundantWarning("/** @type {!number} */ var x;");
+ checkRedundantWarning("/** @type {!string} */ var x;");
+ checkRedundantWarning("/** @type {!symbol} */ var x;");
+ checkRedundantWarning("/** @type {!undefined} */ var x;");
+ checkRedundantWarning("/** @type {!void} */ var x;");
+
+ checkNoWarning("/** @type {?boolean} */ var x;");
+ checkNoWarning("/** @type {boolean} */ var x;");
+ }
+
+ public void testReferenceType() {
+ checkMissingWarning("/** @type {Object} */ var x;");
+ checkMissingWarning("/** @type {Function} */ var x;");
+ checkMissingWarning("/** @type {Symbol} */ var x;");
+
+ checkNoWarning("/** @type {?Object} */ var x;");
+ checkNoWarning("/** @type {!Object} */ var x;");
+ }
+
+ public void testRecordType() {
+ checkRedundantWarning("/** @type {!{foo: string}} */ var x;");
+ checkRedundantWarning("/** @type {{foo: !string}} */ var x;");
+
+ checkMissingWarning("/** @type {{foo: Object}} */ var x;");
+
+ checkNoWarning("/** @type {?{foo: string}} */ var x;");
+ checkNoWarning("/** @type {{foo: string}} */ var x;");
+ checkNoWarning("/** @type {{foo: ?string}} */ var x;");
+ checkNoWarning("/** @type {{foo: !Object}} */ var x;");
+ checkNoWarning("/** @type {{foo: ?Object}} */ var x;");
+ }
+
+ public void testFunctionType() {
+ checkRedundantWarning("/** @type {!function()} */ function f(){}");
+ checkRedundantWarning("/** @type {function(!string)} */ function f(x){}");
+ checkRedundantWarning("/** @type {function(): !string} */ function f(x){}");
+
+ checkMissingWarning("/** @type {function(Object)} */ function f(x){}");
+ checkMissingWarning("/** @type {function(): Object} */ function f(x){}");
+
+ checkNoWarning("/** @type {function()} */ function f(){}");
+ checkNoWarning("/** @type {function(?string): ?string} */ function f(x){}");
+ checkNoWarning("/** @type {function(string): string} */ function f(x){}");
+ checkNoWarning("/** @type {function(!Object): ?Object} */ function f(x){}");
+ checkNoWarning("/** @type {function(new:Object)} */ function f(){}");
+ checkNoWarning("/** @type {function(this:Object)} */ function f(){}");
+ }
+
+ public void testUnionType() {
+ checkRedundantWarning("/** @type {!Object|!string} */ var x;");
+
+ checkMissingWarning("/** @type {Object|string} */ var x;");
+
+ checkNoWarning("/** @type {?Object|string} */ var x;");
+ checkNoWarning("/** @type {!Object|string} */ var x;");
+ }
+
+ public void testEnumType() {
+ checkRedundantWarning("/** @enum {!boolean} */ var x;");
+ checkRedundantWarning("/** @enum {!number} */ var x;");
+ checkRedundantWarning("/** @enum {!string} */ var x;");
+ checkRedundantWarning("/** @enum {!symbol} */ var x;");
+
+ checkNoWarning("/** @enum {?string} */ var x;");
+ checkNoWarning("/** @enum {string} */ var x;");
+ checkNoWarning("/** @enum {Object} */ var x;");
+ checkNoWarning("/** @enum {?Object} */ var x;");
+ checkNoWarning("/** @enum {!Object} */ var x;");
+ }
+
+ public void testTemplateDefinitionType() {
+ checkNoWarning("/** @param {T} x @template T */", "function f(x){}");
+ checkNoWarning("/** @param {S} x @return {T} @template S,T */", "function f(x){}");
+ checkNoWarning(
+ lines(
+ "/** @constructor @template T */ function Foo(){}",
+ "/** @param {T} x */ Foo.prototype.bar = function(x){};"));
+ }
+
+ public void testTemplateInstantiationType() {
+ checkRedundantWarning("/** @type {!Array} */ var x;");
+
+ checkMissingWarning("/** @type {Array} */ var x;");
+ checkMissingWarning("/** @type {!Array