/*
* 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;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Use by the classes that test {@link NewTypeInference}.
*
* @author dimvar@google.com (Dimitris Vardoulakis)
*/
public abstract class NewTypeInferenceTestBase extends CompilerTypeTestCase {
protected CompilerOptions compilerOptions;
protected static final String CLOSURE_BASE =
LINE_JOINER.join(
"/** @const */",
"var goog = {};",
"/** @return {void} */",
"goog.nullFunction = function() {};",
"/** @type {!Function} */",
"goog.abstractMethod = function(){};",
"goog.asserts;",
"goog.asserts.assertInstanceOf;",
"goog.getMsg;",
"goog.addSingletonGetter;",
"goog.reflect;",
"goog.reflect.object;",
"Object.prototype.superClass_;");
protected static final String DEFAULT_EXTERNS =
CompilerTypeTestCase.DEFAULT_EXTERNS + LINE_JOINER.join(
"/**",
" * @param {Object} proto",
" * @param {Object=} opt_properties",
" * @return {!Object}",
" */",
"Object.create = function(proto, opt_properties) {};",
"/** @const {undefined} */",
"var undefined;",
"/**",
" * @this {!String|string}",
" * @param {!RegExp} regexp",
" * @return {!Array<string>}",
" */",
"String.prototype.match = function(regexp) {};",
"/** @return {string} */",
"String.prototype.toString = function() {};",
"/** @return {string} */",
"String.prototype.toLowerCase = function() {};",
"String.prototype.startsWith = function(s) {};",
"/**",
" @param {number=} opt_radix",
" @return {string}",
"*/",
"Number.prototype.toString = function(opt_radix) {};",
"/**",
" * @param {number=} opt_fractionDigits",
" * @return {string}",
" */",
"Number.prototype.toExponential = function(opt_fractionDigits) {};",
"/** @return {string} */",
"Boolean.prototype.toString = function() {};",
"/**",
" * @param {?=} opt_begin",
" * @param {?=} opt_end",
" * @return {!Array.<T>}",
" * @this {{length: number}|string}",
" * @template T",
" */",
"Array.prototype.slice = function(opt_begin, opt_end) {};",
"/**",
" * @param {...?} var_args",
" * @return {!Array.<?>}",
" */",
"Array.prototype.concat = function(var_args) {};",
"/** @interface */",
"function IThenable () {}",
"IThenable.prototype.then = function(onFulfilled) {};",
"/**",
" * @template T",
" * @constructor",
" * @implements {IThenable}",
" */",
"function Promise(resolver) {};",
"/**",
" * @param {VALUE} value",
" * @return {!Promise<VALUE>}",
" * @template VALUE",
" */",
"Promise.resolve = function(value) {};",
"/**",
" * @template RESULT",
" * @param {function(): RESULT} onFulfilled",
" * @return {RESULT}",
" */",
"Promise.prototype.then = function(onFulfilled) {};",
"/**",
" * @constructor",
" * @param {*=} opt_message",
" * @param {*=} opt_file",
" * @param {*=} opt_line",
" * @return {!Error}",
" */",
"function Error(opt_message, opt_file, opt_line) {}",
"/** @type {string} */",
"Error.prototype.stack;",
"/** @constructor */",
"function Window() {}",
"/** @type {boolean} */",
"Window.prototype.closed;",
"/** @type {!Window} */",
"var window;",
"/**",
" * @param {Function|string} callback",
" * @param {number=} opt_delay",
" * @param {...*} var_args",
" * @return {number}",
" */",
"function setTimeout(callback, opt_delay, var_args) {}",
"/**",
" * @constructor",
" * @extends {Array<string>}",
" */",
"var ITemplateArray = function() {};",
"",
"/** @type {!Array<string>} */",
"ITemplateArray.prototype.raw;",
"",
"/**",
" * @param {!ITemplateArray} template",
" * @param {...*} var_args",
" * @return {string}",
" */",
"String.raw = function(template, var_args) {};",
"");
@Override
protected void setUp() throws Exception {
super.setUp();
compilerOptions = getDefaultOptions();
}
@Override
protected CompilerOptions getDefaultOptions() {
CompilerOptions compilerOptions = super.getDefaultOptions();
compilerOptions.setClosurePass(true);
compilerOptions.setNewTypeInference(true);
compilerOptions.setWarningLevel(
DiagnosticGroups.NEW_CHECK_TYPES_ALL_CHECKS, CheckLevel.WARNING);
// EC5 is the highest language level that type inference understands.
compilerOptions.setLanguage(LanguageMode.ECMASCRIPT5);
return compilerOptions;
}
protected final PassFactory makePassFactory(
String name, final CompilerPass pass) {
return new PassFactory(name, true/* one-time pass */) {
@Override
protected CompilerPass create(AbstractCompiler compiler) {
return pass;
}
};
}
private final void parseAndTypeCheck(String externs, String js) {
initializeNewCompiler(compilerOptions);
compiler.init(
ImmutableList.of(SourceFile.fromCode("[externs]", externs)),
ImmutableList.of(SourceFile.fromCode("[testcode]", js)),
compilerOptions);
Node externsRoot = IR.root();
externsRoot.addChildToFront(
compiler.getInput(new InputId("[externs]")).getAstRoot(compiler));
Node astRoot = IR.root();
astRoot.addChildToFront(
compiler.getInput(new InputId("[testcode]")).getAstRoot(compiler));
assertEquals("parsing error: " + Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
assertEquals(
"parsing warning: " + Joiner.on(", ").join(compiler.getWarnings()), 0,
compiler.getWarningCount());
// Create common parent of externs and ast; needed by Es6RewriteBlockScopedDeclaration.
Node block = IR.root(externsRoot, astRoot);
// Run ASTValidator
(new AstValidator(compiler)).validateRoot(block);
DeclaredGlobalExternsOnWindow rewriteExterns =
new DeclaredGlobalExternsOnWindow(compiler);
List<PassFactory> passes = new ArrayList<>();
passes.add(makePassFactory("globalExternsOnWindow", rewriteExterns));
ProcessClosurePrimitives closurePass =
new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false);
passes.add(makePassFactory("ProcessClosurePrimitives", closurePass));
if (compilerOptions.getLanguageIn() == LanguageMode.ECMASCRIPT6_TYPED) {
passes.add(makePassFactory("convertEs6TypedToEs6",
new Es6TypedToEs6Converter(compiler)));
}
if (compilerOptions.getLanguageIn().isEs6OrHigher()
&& !compilerOptions.getLanguageOut().isEs6OrHigher()) {
TranspilationPasses.addEs6EarlyPasses(passes);
TranspilationPasses.addEs6LatePasses(passes);
TranspilationPasses.addRewritePolyfillPass(passes);
}
passes.add(makePassFactory("GlobalTypeInfo", compiler.getSymbolTable()));
passes.add(makePassFactory("NewTypeInference", new NewTypeInference(compiler)));
PhaseOptimizer phaseopt = new PhaseOptimizer(compiler, null);
phaseopt.consume(passes);
phaseopt.process(externsRoot, astRoot);
}
protected final void typeCheck(String js, DiagnosticType... warningKinds) {
typeCheck(DEFAULT_EXTERNS, js, warningKinds);
}
protected final void typeCheckCustomExterns(
String externs, String js, DiagnosticType... warningKinds) {
typeCheck(externs, js, warningKinds);
}
private final void typeCheck(
String externs, String js, DiagnosticType... warningKinds) {
parseAndTypeCheck(externs, js);
JSError[] warnings = compiler.getWarnings();
JSError[] errors = compiler.getErrors();
String errorMessage = LINE_JOINER.join(
"Expected warning of type:",
"================================================================",
LINE_JOINER.join(warningKinds),
"================================================================",
"but found:",
"----------------------------------------------------------------",
LINE_JOINER.join(errors) + "\n" + LINE_JOINER.join(warnings),
"----------------------------------------------------------------\n");
assertEquals(
errorMessage + "Warning count",
warningKinds.length,
warnings.length + errors.length);
for (JSError warning : warnings) {
assertTrue(
"Wrong warning type\n" + errorMessage,
Arrays.asList(warningKinds).contains(warning.getType()));
}
for (JSError error : errors) {
assertTrue(
"Wrong warning type\n" + errorMessage,
Arrays.asList(warningKinds).contains(error.getType()));
}
}
// Used only in the cases where we provide extra details in the error message.
// Don't use in other cases.
// It is deliberately less general; no custom externs and only a single
// warning per test.
protected final void typeCheckMessageContents(
String js, DiagnosticType warningKind, String warningMsg) {
parseAndTypeCheck(DEFAULT_EXTERNS, js);
JSError[] warnings = compiler.getWarnings();
JSError[] errors = compiler.getErrors();
assertEquals(
"Expected no errors, but found:\n" + Arrays.toString(errors),
0, errors.length);
assertEquals(
"Expected one warning, but found:\n" + Arrays.toString(warnings),
1, warnings.length);
JSError warning = warnings[0];
assertEquals(LINE_JOINER.join(
"Wrong warning type",
"Expected warning of type:",
"================================================================",
warningKind.toString(),
"================================================================",
"but found:",
"----------------------------------------------------------------",
warning.toString(),
"----------------------------------------------------------------\n"),
warningKind, warning.getType());
assertEquals(LINE_JOINER.join(
"Wrong warning message",
"Expected:",
"================================================================",
warningMsg,
"================================================================",
"but found:",
"----------------------------------------------------------------",
warning.description,
"----------------------------------------------------------------\n"),
warningMsg, warning.description);
}
}