diff --git a/src/com/google/javascript/jscomp/DefaultPassConfig.java b/src/com/google/javascript/jscomp/DefaultPassConfig.java
index 336de8a8aaa..cae6270e27d 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 f092d8f313d..21a97e2ba33 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,11 +580,10 @@ 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,
@@ -660,8 +659,7 @@ 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);
@@ -672,14 +670,16 @@ 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 1829a919f66..92d1389af1b 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 CheckNullabilityModifiers(compiler),
+ new CheckRedundantNullabilityModifier(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
deleted file mode 100644
index ca35653b64a..00000000000
--- a/src/com/google/javascript/jscomp/lint/CheckNullabilityModifiers.java
+++ /dev/null
@@ -1,164 +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.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;
-
-/**
- * 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.");
-
- private static final ImmutableSet PRIMITIVE_TYPE_NAMES =
- ImmutableSet.of("boolean", "number", "string", "symbol", "undefined", "null");
-
- private final AbstractCompiler compiler;
-
- 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(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()) {
- // 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.
- checkTypeExpression(t, info.getEnumParameterType(), false);
- }
- if (info.hasTypedefType()) {
- checkTypeExpression(t, 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.
- checkTypeExpression(t, info.getThisType(), true);
- }
- for (JSTypeExpression expr : info.getThrownTypes()) {
- checkTypeExpression(t, expr, false);
- }
- // JSDocInfoParser enforces the @extends and @implements types to be unqualified in the source
- // code, so we don't need to check them.
- }
-
- private static void checkTypeExpression(
- final NodeTraversal t, JSTypeExpression expr, boolean hasArtificialTopLevelBang) {
- final Node root = expr.getRoot();
- NodeUtil.visitPreOrder(
- root,
- 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) {
- t.report(node, MISSING_NULLABILITY_MODIFIER_JSDOC, getReportedTypeName(node));
- } else if (isPrimitiveOrLiteral && hasNonArtificialBang) {
- t.report(parent, REDUNDANT_NULLABILITY_MODIFIER_JSDOC, getReportedTypeName(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
new file mode 100644
index 00000000000..0f2b77d1942
--- /dev/null
+++ b/src/com/google/javascript/jscomp/lint/CheckRedundantNullabilityModifier.java
@@ -0,0 +1,118 @@
+/*
+ * 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
deleted file mode 100644
index b397b3876eb..00000000000
--- a/test/com/google/javascript/jscomp/lint/CheckNullabilityModifiersTest.java
+++ /dev/null
@@ -1,207 +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.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;");
-
- 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 testTemplateType() {
- checkRedundantWarning("/** @type {!Array} */ var x;");
-
- checkMissingWarning("/** @type {Array} */ var x;");
- checkMissingWarning("/** @type {!Array