/*
* 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.lint;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.CLASS_DISALLOWED_JSDOC;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.CONSTRUCTOR_DISALLOWED_JSDOC;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.EXTERNS_FILES_SHOULD_BE_ANNOTATED;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.INCORRECT_PARAM_NAME;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.INVALID_SUPPRESS;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MISSING_JSDOC;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MISSING_PARAMETER_JSDOC;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MISSING_RETURN_JSDOC;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MIXED_PARAM_JSDOC_STYLES;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MUST_BE_PRIVATE;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.MUST_HAVE_TRAILING_UNDERSCORE;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.OPTIONAL_PARAM_NOT_MARKED_OPTIONAL;
import static com.google.javascript.jscomp.lint.CheckJSDocStyle.WRONG_NUMBER_OF_PARAMS;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.ClosureCodingConvention;
import com.google.javascript.jscomp.CodingConvention;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.CompilerPass;
import com.google.javascript.jscomp.CompilerTestCase;
import com.google.javascript.jscomp.GoogleCodingConvention;
/**
* Test case for {@link CheckJSDocStyle}.
*/
public final class CheckJSDocStyleTest extends CompilerTestCase {
public CheckJSDocStyleTest() {
super("/** @fileoverview\n * @externs\n */");
}
private CodingConvention codingConvention;
@Override
public void setUp() throws Exception {
super.setUp();
codingConvention = new GoogleCodingConvention();
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT);
}
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new CheckJSDocStyle(compiler);
}
@Override
protected CompilerOptions getOptions(CompilerOptions options) {
super.getOptions(options);
options.setWarningLevel(CheckJSDocStyle.ALL_DIAGNOSTICS, CheckLevel.WARNING);
return options;
}
@Override
protected CodingConvention getCodingConvention() {
return codingConvention;
}
public void testInvalidSuppress() {
testSame("/** @suppress {missingRequire} */ var x = new y.Z();");
testSame("/** @suppress {missingRequire} */ function f() { var x = new y.Z(); }");
testSame("/** @suppress {missingRequire} */ var f = function() { var x = new y.Z(); }");
testSame(
LINE_JOINER.join(
"var obj = {",
" /** @suppress {uselessCode} */",
" f: function() {},",
"}"));
testSame(
LINE_JOINER.join(
"var obj = {",
" /** @suppress {uselessCode} */",
" f() {},",
"}"));
testSame(
LINE_JOINER.join(
"class Example {",
" /** @suppress {uselessCode} */",
" f() {}",
"}"));
testSame(
LINE_JOINER.join(
"class Example {",
" /** @suppress {uselessCode} */",
" static f() {}",
"}"));
testSame(
LINE_JOINER.join(
"class Example {",
" /** @suppress {uselessCode} */",
" get f() {}",
"}"));
testSame(
LINE_JOINER.join(
"class Example {",
" /**",
" * @param {string} val",
" * @suppress {uselessCode}",
" */",
" set f(val) {}",
"}"));
testWarning("/** @suppress {uselessCode} */ goog.require('unused.Class');", INVALID_SUPPRESS);
testSame("/** @suppress {extraRequire} */ goog.require('unused.Class');");
testSame("/** @const @suppress {duplicate} */ var google = {};");
testSame("/** @suppress {const} */ var google = {};");
}
public void testExtraneousClassAnnotations() {
testWarning(
LINE_JOINER.join(
"/**",
" * @constructor",
" */",
"var X = class {};"),
CLASS_DISALLOWED_JSDOC);
testWarning(
LINE_JOINER.join(
"/**",
" * @constructor",
" */",
"class X {};"),
CLASS_DISALLOWED_JSDOC);
// TODO(tbreisacher): Warn for @extends too. We need to distinguish between cases like this
// which are totally redundant...
testSame(
LINE_JOINER.join(
"/**",
" * @extends {Y}",
" */",
"class X extends Y {};"));
// ... and ones like this which are not.
testSame(
LINE_JOINER.join(
"/**",
" * @extends {Y<number>}",
" */",
"class X extends Y {};"));
testSame(
LINE_JOINER.join(
"/**",
" * @implements {Z}",
" */",
"class X extends Y {};"));
testSame(
LINE_JOINER.join(
"/**",
" * @interface",
" * @extends {Y}",
" */",
"class X extends Y {};"));
testSame(
LINE_JOINER.join(
"/**",
" * @record",
" * @extends {Y}",
" */",
"class X extends Y {};"));
}
public void testNestedArrowFunctions() {
testSame(
LINE_JOINER.join(
"/**",
" * @param {Object} a",
" * @return {function(Object): boolean}",
" */",
"var haskellStyleEquals = a => b => a == b;"));
}
public void testGetterSetterMissingJsDoc() {
testWarning("class Foo { get twentyone() { return 21; } }", MISSING_JSDOC);
testWarning("class Foo { set someString(s) { this.someString_ = s; } }", MISSING_JSDOC);
testSame("class Foo { /** @return {number} */ get twentyone() { return 21; } }");
testSame("class Foo { /** @param {string} s */ set someString(s) { this.someString_ = s; } }");
}
public void testMissingJsDoc() {
testWarning("function f() {}", MISSING_JSDOC);
testWarning("var f = function() {}", MISSING_JSDOC);
testWarning("let f = function() {}", MISSING_JSDOC);
testWarning("const f = function() {}", MISSING_JSDOC);
testWarning("foo.bar = function() {}", MISSING_JSDOC);
testWarning("Foo.prototype.bar = function() {}", MISSING_JSDOC);
testWarning("class Foo { bar() {} }", MISSING_JSDOC);
testWarning("class Foo { constructor(x) {} }", MISSING_JSDOC);
testWarning("var Foo = class { bar() {} };", MISSING_JSDOC);
testWarning("if (COMPILED) { var f = function() {}; }", MISSING_JSDOC);
testWarning("var f = async function() {};", MISSING_JSDOC);
testWarning("async function f() {};", MISSING_JSDOC);
testWarning("Polymer({ method() {} });", MISSING_JSDOC);
testWarning("Polymer({ method: function() {} });", MISSING_JSDOC);
testSame("/** @return {string} */ function f() {}");
testSame("/** @return {string} */ var f = function() {}");
testSame("/** @return {string} */ let f = function() {}");
testSame("/** @return {string} */ const f = function() {}");
testSame("/** @return {string} */ foo.bar = function() {}");
testSame("/** @return {string} */ Foo.prototype.bar = function() {}");
testSame("class Foo { /** @return {string} */ bar() {} }");
testSame("class Foo { constructor(/** string */ x) {} }");
testSame("var Foo = class { /** @return {string} */ bar() {} };");
testSame("/** @param {string} s */ var f = async function(s) {};");
testSame("/** @param {string} s */ async function f(s) {};");
testSame("Polymer({ /** @return {null} */ method() {} });");
testSame("Polymer({ /** @return {null} */ method: function() {} });");
}
public void testMissingJsDoc_noWarningIfInlineJsDocIsPresent() {
testSame("function /** string */ f() {}");
testSame("function f(/** string */ x) {}");
testSame("var f = function(/** string */ x) {}");
testSame("let f = function(/** string */ x) {}");
testSame("const f = function(/** string */ x) {}");
testSame("foo.bar = function(/** string */ x) {}");
testSame("Foo.prototype.bar = function(/** string */ x) {}");
testSame("class Foo { bar(/** string */ x) {} }");
testSame("var Foo = class { bar(/** string */ x) {} };");
}
public void testMissingJsDoc_noWarningIfNotTopLevel() {
testSame(inIIFE("function f() {}"));
testSame(inIIFE("var f = function() {}"));
testSame(inIIFE("let f = function() {}"));
testSame(inIIFE("const f = function() {}"));
testSame(inIIFE("foo.bar = function() {}"));
testSame(inIIFE("class Foo { bar() {} }"));
testSame(inIIFE("var Foo = class { bar() {} };"));
testSame("myArray.forEach(function(elem) { alert(elem); });");
testSame(LINE_JOINER.join(
"Polymer({",
" is: 'example-elem',",
" /** @return {null} */",
" someMethod: function() {},",
"});"));
testSame(LINE_JOINER.join(
"Polymer({",
" is: 'example-elem',",
" /** @return {null} */",
" someMethod() {},",
"});"));
}
public void testMissingJsDoc_noWarningIfNotTopLevelAndNoParams() {
testSame(LINE_JOINER.join(
"describe('a karma test', function() {",
" /** @ngInject */",
" var helperFunction = function($compile, $rootScope) {};",
"})"));
}
public void testMissingJsDoc_noWarningOnTestFunctions() {
testSame("function testSomeFunctionality() {}");
testSame("var testSomeFunctionality = function() {};");
testSame("let testSomeFunctionality = function() {};");
testSame("window.testSomeFunctionality = function() {};");
testSame("const testSomeFunctionality = function() {};");
testSame("function setUp() {}");
testSame("function tearDown() {}");
testSame("var setUp = function() {};");
testSame("var tearDown = function() {};");
}
public void testMissingJsDoc_noWarningOnEmptyConstructor() {
testSame("class Foo { constructor() {} }");
}
public void testMissingJsDoc_googModule() {
testWarning("goog.module('a.b.c'); function f() {}", MISSING_JSDOC);
testWarning("goog.module('a.b.c'); var f = function() {};", MISSING_JSDOC);
}
public void testMissingJsDoc_googModule_noWarning() {
testSame("goog.module('a.b.c'); /** @type {function()} */ function f() {}");
testSame("goog.module('a.b.c'); /** @type {function()} */ var f = function() {};");
}
private static String inIIFE(String js) {
return "(function() {\n" + js + "\n})()";
}
public void testMissingParam_noWarning() {
testSame(LINE_JOINER.join(
"/**",
" * @param {string} x",
" * @param {string} y",
" */",
"function f(x, y) {}"));
testSame(LINE_JOINER.join(
"/** @override */",
"Foo.bar = function(x, y) {}"));
testSame(LINE_JOINER.join(
"/**",
" * @param {string=} x",
" */",
"function f(x = 1) {}"));
testSame(LINE_JOINER.join(
"/**",
" * @param {number=} x",
" * @param {number=} y",
" * @param {number=} z",
" */",
"function f(x = 1, y = 2, z = 3) {}"));
testSame(LINE_JOINER.join(
"/**",
" * @param {...string} args",
" */",
"function f(...args) {}"));
testSame(LINE_JOINER.join(
"(function() {",
" myArray.forEach(function(elem) { alert(elem); });",
"})();"));
testSame(LINE_JOINER.join(
"(function() {",
" myArray.forEach(elem => alert(elem));",
"})();"));
testSame("/** @type {function(number)} */ function f(x) {}");
testSame("function f(/** string */ inlineArg) {}");
testSame("/** @export */ function f(/** string */ inlineArg) {}");
testSame("class Foo { constructor(/** string */ inlineArg) {} }");
testSame("class Foo { method(/** string */ inlineArg) {} }");
testSame("/** @export */ class Foo { constructor(/** string */ inlineArg) {} }");
testSame("class Foo { /** @export */ method(/** string */ inlineArg) {} }");
}
public void testMissingParam() {
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} x",
// No @param for y.
" */",
"function f(x, y) {}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} x",
" */",
"function f(x = 1) {}"),
OPTIONAL_PARAM_NOT_MARKED_OPTIONAL);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} x",
// No @param for y.
" */",
"function f(x, y = 1) {}"),
WRONG_NUMBER_OF_PARAMS);
testWarning("function f(/** string */ x, y) {}", MISSING_PARAMETER_JSDOC);
testWarning("function f(x, /** string */ y) {}", MISSING_PARAMETER_JSDOC);
testWarning("function /** string */ f(x) {}", MISSING_PARAMETER_JSDOC);
testWarning(inIIFE("function f(/** string */ x, y) {}"), MISSING_PARAMETER_JSDOC);
testWarning(inIIFE("function f(x, /** string */ y) {}"), MISSING_PARAMETER_JSDOC);
testWarning(inIIFE("function /** string */ f(x) {}"), MISSING_PARAMETER_JSDOC);
}
public void testMissingParamWithDestructuringPattern() {
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f(namedParam, {destructuring:pattern}) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f({destructuring:pattern}, namedParam) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f(namedParam, [pattern]) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f([pattern], namedParam) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {{",
" * a: (string|undefined),",
" * b: (number|undefined),",
" * c: (boolean|undefined)",
" * }} obj",
" */",
"function create({a = 'hello', b = 8, c = false} = {}) {}"),
OPTIONAL_PARAM_NOT_MARKED_OPTIONAL);
// Same as above except there's an '=' to indicate that it's optional.
testSame(
LINE_JOINER.join(
"/**",
" * @param {{",
" * a: (string|undefined),",
" * b: (number|undefined),",
" * c: (boolean|undefined)",
" * }=} obj",
" */",
"function create({a = 'hello', b = 8, c = false} = {}) {}"));
}
public void testMissingParamWithDestructuringPatternWithDefault() {
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f(namedParam, {destructuring:pattern} = defaultValue) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
testWarning(
LINE_JOINER.join(
"/**",
" * @param {string} namedParam",
" * @return {void}",
" */",
"function f(namedParam, [pattern] = defaultValue) {",
"}"),
WRONG_NUMBER_OF_PARAMS);
}
public void testParamWithNoTypeInfo() {
testSame(
LINE_JOINER.join(
"/**",
" * @param x A param with no type information.",
" */",
"function f(x) { }"));
}
public void testMissingPrivate_noWarningWithClosureConvention() {
codingConvention = new ClosureCodingConvention();
testSame(
LINE_JOINER.join(
"/**",
" * @return {number}",
" * @private",
" */",
"X.prototype.foo = function() { return 0; }"));
}
public void testMissingPrivate() {
testWarning(
LINE_JOINER.join(
"/** @return {number} */",
"X.prototype.foo_ = function() { return 0; }"),
MUST_BE_PRIVATE);
testWarning(
LINE_JOINER.join(
"/** @type {?number} */",
"X.prototype.foo_ = null;"),
MUST_BE_PRIVATE);
testWarning(
LINE_JOINER.join(
"/**",
" * @return {number}",
" * @private",
" */",
"X.prototype.foo = function() { return 0; }"),
MUST_HAVE_TRAILING_UNDERSCORE);
testWarning(
LINE_JOINER.join(
"/**",
" * @type {number}",
" * @private",
" */",
"X.prototype.foo = 0;"),
MUST_HAVE_TRAILING_UNDERSCORE);
testSame(
LINE_JOINER.join(
"/**",
" * @return {number}",
" * @private",
" */",
"X.prototype.foo_ = function() { return 0; }"));
testSame(
LINE_JOINER.join(
"/**",
" * @type {number}",
" * @private",
" */",
"X.prototype.foo_ = 0;"));
testSame(
LINE_JOINER.join(
"/** @type {number} */",
"X.prototype['@some_special_property'] = 0;"));
}
public void testMissingPrivate_class() {
testWarning(
LINE_JOINER.join(
"class Example {",
" /** @return {number} */",
" foo_() { return 0; }",
"}"),
MUST_BE_PRIVATE);
testWarning(
LINE_JOINER.join(
"class Example {",
" /** @return {number} */",
" get foo_() { return 0; }",
"}"),
MUST_BE_PRIVATE);
testWarning(
LINE_JOINER.join(
"class Example {",
" /** @param {number} val */",
" set foo_(val) {}",
"}"),
MUST_BE_PRIVATE);
testWarning(
LINE_JOINER.join(
"class Example {",
" /**",
" * @return {number}",
" * @private",
" */",
" foo() { return 0; }",
"}"),
MUST_HAVE_TRAILING_UNDERSCORE);
testWarning(
LINE_JOINER.join(
"class Example {",
" /**",
" * @return {number}",
" * @private",
" */",
" get foo() { return 0; }",
"}"),
MUST_HAVE_TRAILING_UNDERSCORE);
testWarning(
LINE_JOINER.join(
"class Example {",
" /**",
" * @param {number} val",
" * @private",
" */",
" set foo(val) { }",
"}"),
MUST_HAVE_TRAILING_UNDERSCORE);
}
public void testMissingPrivate_dontWarnOnObjectLiteral() {
testSame(
LINE_JOINER.join(
"var obj = {",
" /** @return {number} */",
" foo_() { return 0; }",
"}"));
}
public void testOptionalArgs() {
testSame(
LINE_JOINER.join(
"/**",
" * @param {number=} n",
" */",
"function f(n) {}"));
testSame(
LINE_JOINER.join(
"/**",
" * @param {number} opt_n",
" */",
"function f(opt_n) {}"),
OPTIONAL_PARAM_NOT_MARKED_OPTIONAL);
testSame(LINE_JOINER.join(
"/**",
" * @param {number=} opt_n",
" */",
"function f(opt_n) {}"));
}
public void testParamsOutOfOrder() {
testWarning(
LINE_JOINER.join(
"/**",
" * @param {?} second",
" * @param {?} first",
" */",
"function f(first, second) {}"),
INCORRECT_PARAM_NAME);
}
public void testMixedStyles() {
testWarning(
LINE_JOINER.join(
"/**",
" * @param {?} first",
" * @param {string} second",
" */",
"function f(first, /** string */ second) {}"),
MIXED_PARAM_JSDOC_STYLES);
}
public void testDestructuring() {
testSame(
LINE_JOINER.join(
"/**",
" * @param {{x: number, y: number}} point",
" */",
"function getDistanceFromZero({x, y}) {}"));
testSame("function getDistanceFromZero(/** {x: number, y: number} */ {x, y}) {}");
}
public void testMissingReturn_functionStatement_noWarning() {
testSame("/** @param {number} x */ function f(x) {}");
testSame("/** @constructor */ function f() {}");
testSame("/** @param {number} x */ function f(x) { function bar() { return x; } }");
testSame("/** @param {number} x */ function f(x) { return; }");
testSame("/** @param {number} x @return {number} */ function f(x) { return x; }");
testSame("/** @param {number} x */ function /** number */ f(x) { return x; }");
testSame("/** @inheritDoc */ function f(x) { return x; }");
testSame("/** @override */ function f(x) { return x; }");
}
public void testMissingReturn_assign_noWarning() {
testSame("/** @param {number} x */ f = function(x) {}");
testSame("/** @constructor */ f = function() {}");
testSame("/** @param {number} x */ f = function(x) { function bar() { return x; } }");
testSame("/** @param {number} x */ f = function(x) { return; }");
testSame("/** @param {number} x @return {number} */ f = function(x) { return x; }");
testSame("/** @inheritDoc */ f = function(x) { return x; }");
testSame("/** @override */ f = function(x) { return x; }");
}
public void testMissingReturn_var_noWarning() {
testSame("/** @param {number} x */ var f = function(x) {}");
testSame("/** @constructor */ var f = function() {}");
testSame("/** @param {number} x */ var f = function(x) { function bar() { return x; } }");
testSame("/** @param {number} x */ var f = function(x) { return; }");
testSame("/** @param {number} x @return {number} */ var f = function(x) { return x; }");
testSame("/** @const {function(number): number} */ var f = function(x) { return x; }");
testSame("/** @inheritDoc */ var f = function(x) { return x; }");
testSame("/** @override */ var f = function(x) { return x; }");
}
public void testMissingReturn_functionStatement() {
testWarning("/** @param {number} x */ function f(x) { return x; }", MISSING_RETURN_JSDOC);
testWarning(
LINE_JOINER.join(
"/** @param {number} x */",
"function f(x) {",
" /** @param {number} x */",
" function bar(x) {",
" return x;",
" }",
"}"),
MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x */ function f(x) { if (true) { return x; } }", MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x @constructor */ function f(x) { return x; }", MISSING_RETURN_JSDOC);
}
public void testMissingReturn_assign() {
testWarning("/** @param {number} x */ f = function(x) { return x; }", MISSING_RETURN_JSDOC);
testWarning(
LINE_JOINER.join(
"/** @param {number} x */",
"function f(x) {",
" /** @param {number} x */",
" bar = function(x) {",
" return x;",
" }",
"}"),
MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x */ f = function(x) { if (true) { return x; } }",
MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x @constructor */ f = function(x) { return x; }",
MISSING_RETURN_JSDOC);
}
public void testMissingReturn_var() {
testWarning("/** @param {number} x */ var f = function(x) { return x; }", MISSING_RETURN_JSDOC);
testWarning(
LINE_JOINER.join(
"/** @param {number} x */",
"function f(x) {",
" /** @param {number} x */",
" var bar = function(x) {",
" return x;",
" }",
"}"),
MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x */ var f = function(x) { if (true) { return x; } }",
MISSING_RETURN_JSDOC);
testWarning(
"/** @param {number} x @constructor */ var f = function(x) { return x; }",
MISSING_RETURN_JSDOC);
}
public void testExternsAnnotation() {
testSame(
"function Example() {}",
"",
EXTERNS_FILES_SHOULD_BE_ANNOTATED);
testSame(
"/** @fileoverview Some super cool externs.\n * @externs\n */ function Example() {}",
"",
null);
testSame(
LINE_JOINER.join(
"/** @fileoverview Some super cool externs.\n * @externs\n */",
"/** @constructor */ function Example() {}",
"/** @param {number} x */ function example2(x) {}"),
"",
null);
test(
new String[] {
"/** @fileoverview Some externs.\n * @externs\n */ /** @const */ var example;",
"/** @fileoverview Some more.\n * @externs\n */ /** @const */ var example2;",
},
new String[] {},
null, null);
}
public void testConstructorsDontHaveVisibility() {
testSame(inIIFE("/** @private */ class Foo { constructor() {} }"));
testWarning(
inIIFE("class Foo { /** @private */ constructor() {} }"), CONSTRUCTOR_DISALLOWED_JSDOC);
}
}