com.google.javascript.jscomp.CheckProvides Maven / Gradle / Ivy
/*
* Copyright 2008 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.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node;
import java.util.HashMap;
import java.util.Map;
/**
* Ensures that '@constructor X' has a 'goog.provide("X")' .
*/
class CheckProvides implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
private final CodingConvention codingConvention;
static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.warning(
"JSC_MISSING_PROVIDE",
"missing goog.provide(''{0}'')");
CheckProvides(AbstractCompiler compiler) {
this.compiler = compiler;
this.codingConvention = compiler.getCodingConvention();
}
@Override
public void process(Node externs, Node root) {
hotSwapScript(root, null);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
CheckProvidesCallback callback =
new CheckProvidesCallback(codingConvention);
NodeTraversal.traverse(compiler, scriptRoot, callback);
}
private class CheckProvidesCallback implements Callback {
private final Map provides = new HashMap<>();
private final Map ctors = new HashMap<>();
private final CodingConvention convention;
private boolean containsRequires = false;
CheckProvidesCallback(CodingConvention convention){
this.convention = convention;
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body. We also don't care about modules.
if (n.isModuleBody()) {
return false;
}
return parent == null || !parent.isFunction() || n == parent.getFirstChild();
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case CALL:
String providedClassName =
codingConvention.extractClassNameIfProvide(n, parent);
if (providedClassName != null) {
provides.put(providedClassName, n);
}
if (!containsRequires && codingConvention.extractClassNameIfRequire(n, parent) != null) {
containsRequires = true;
}
break;
case FUNCTION:
// Arrow function can't be constructors
if (!n.isArrowFunction()) {
visitFunctionNode(n, parent);
}
break;
case CLASS:
visitClassNode(n);
break;
case SCRIPT:
visitScriptNode();
break;
default:
break;
}
}
private void visitFunctionNode(Node n, Node parent) {
// TODO(user): Use isPrivate method below to recognize all functions.
Node name = null;
JSDocInfo info = parent.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = parent.getFirstChild();
} else {
// look to the child, maybe it's a named function
info = n.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitClassNode(Node classNode) {
String name = NodeUtil.getName(classNode);
if (name != null && !isPrivate(classNode)) {
ctors.put(name, classNode);
}
}
private boolean isPrivate(Node classOrFn) {
JSDocInfo info = NodeUtil.getBestJSDocInfo(classOrFn);
if (info != null && info.getVisibility().equals(JSDocInfo.Visibility.PRIVATE)) {
return true;
}
return compiler.getCodingConvention().isPrivate(NodeUtil.getName(classOrFn));
}
private void visitScriptNode() {
for (Map.Entry ctorEntry : ctors.entrySet()) {
String ctorName = ctorEntry.getKey();
int index = -1;
boolean found = false;
if (ctorName.startsWith("$jscomp.")
|| ClosureRewriteModule.isModuleContent(ctorName)
|| ClosureRewriteModule.isModuleExport(ctorName)) {
continue;
}
do {
index = ctorName.indexOf('.', index + 1);
String provideKey = index == -1 ? ctorName : ctorName.substring(0, index);
if (provides.containsKey(provideKey)) {
found = true;
break;
}
} while (index != -1);
if (!found && (containsRequires || !provides.isEmpty())) {
Node n = ctorEntry.getValue();
compiler.report(
JSError.make(n, MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
}
provides.clear();
ctors.clear();
containsRequires = false;
}
}
}