com.google.javascript.jscomp.lint.CheckRequiresAndProvidesSorted Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.frontend.js.minifier
Show all versions of com.liferay.frontend.js.minifier
Liferay Frontend JS Minifier
/*
* 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.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
import com.google.javascript.jscomp.AbstractCompiler;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.HotSwapCompilerPass;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Checks that goog.require() and goog.provide() calls are sorted alphabetically.
*/
public final class CheckRequiresAndProvidesSorted extends AbstractShallowCallback
implements HotSwapCompilerPass {
public static final DiagnosticType REQUIRES_NOT_SORTED =
DiagnosticType.warning("JSC_REQUIRES_NOT_SORTED",
"goog.require() statements are not sorted."
+ " The correct order is:\n\n{0}\n");
public static final DiagnosticType PROVIDES_NOT_SORTED =
DiagnosticType.warning("JSC_PROVIDES_NOT_SORTED",
"goog.provide() statements are not sorted."
+ " The correct order is:\n\n{0}\n");
public static final DiagnosticType PROVIDES_AFTER_REQUIRES =
DiagnosticType.warning(
"JSC_PROVIDES_AFTER_REQUIRES",
"goog.provide() statements should be before goog.require() statements.");
public static final DiagnosticType DUPLICATE_REQUIRE =
DiagnosticType.warning("JSC_DUPLICATE_REQUIRE", "''{0}'' required more than once.");
private final List requires;
private final List provides;
private final AbstractCompiler compiler;
public CheckRequiresAndProvidesSorted(AbstractCompiler compiler) {
this.compiler = compiler;
this.requires = new ArrayList<>();
this.provides = new ArrayList<>();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverseEs6(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverseEs6(compiler, scriptRoot, this);
}
public static final Function getSortKey =
new Function() {
@Override
public String apply(Node n) {
String key = null;
boolean isForwardDeclare = false;
if (NodeUtil.isNameDeclaration(n)) {
if (n.getFirstChild().isName()) {
// Case 1:
// var x = goog.require('w.x');
// or
// var x = goog.forwardDeclare('w.x');
key = n.getFirstChild().getString();
if (n.getFirstFirstChild()
.getFirstChild()
.matchesQualifiedName("goog.forwardDeclare")) {
isForwardDeclare = true;
}
} else if (n.getFirstChild().isDestructuringLhs()) {
// Case 2: var {y} = goog.require('w.x');
// All case 2 nodes should come after all case 1 nodes. ('{' sorts after a-z)
Node pattern = n.getFirstFirstChild();
checkState(pattern.isObjectPattern(), pattern);
Node call = n.getFirstChild().getLastChild();
checkState(call.isCall(), call);
checkState(
call.getFirstChild().matchesQualifiedName("goog.require"), call.getFirstChild());
if (!pattern.hasChildren()) {
key = "{";
} else {
key = "{" + pattern.getFirstChild().getString();
}
}
} else if (n.isExprResult()) {
// Case 3, one of:
// goog.provide('a.b.c');
// goog.require('a.b.c');
// goog.forwardDeclare('a.b.c');
// All case 3 nodes should come after case 1 and 2 nodes, so prepend
// '|' which sorts after '{'
key = "|" + n.getFirstChild().getLastChild().getString();
if (n.getFirstFirstChild().matchesQualifiedName("goog.forwardDeclare")) {
isForwardDeclare = true;
}
} else {
throw new IllegalArgumentException("Unexpected node " + n);
}
// Make sure all forwardDeclares come after all requires.
return (isForwardDeclare ? "z" : "a") + checkNotNull(key);
}
};
private static final String getNamespace(Node requireStatement) {
if (requireStatement.isExprResult()) {
// goog.require('a.b.c');
return requireStatement.getFirstChild().getLastChild().getString();
} else if (NodeUtil.isNameDeclaration(requireStatement)) {
if (requireStatement.getFirstChild().isName()) {
// const x = goog.require('a.b.c');
return requireStatement.getFirstFirstChild().getLastChild().getString();
} else if (requireStatement.getFirstChild().isDestructuringLhs()) {
// const {x} = goog.require('a.b.c');
return requireStatement.getFirstChild().getLastChild().getLastChild().getString();
}
}
throw new IllegalArgumentException("Unexpected node " + requireStatement);
}
private final Ordering alphabetical = Ordering.natural().onResultOf(getSortKey);
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) {
case SCRIPT:
// Duplicate provides are already checked in ProcessClosurePrimitives.
checkForDuplicates(requires);
reportIfOutOfOrder(requires, REQUIRES_NOT_SORTED);
reportIfOutOfOrder(provides, PROVIDES_NOT_SORTED);
reset();
break;
case CALL:
Node callee = n.getFirstChild();
if (!callee.matchesQualifiedName("goog.require")
&& !callee.matchesQualifiedName("goog.forwardDeclare")
&& !callee.matchesQualifiedName("goog.provide")
&& !callee.matchesQualifiedName("goog.module")) {
return;
}
if (parent.isExprResult() && NodeUtil.isTopLevel(parent.getParent())) {
Node namespaceNode = n.getLastChild();
if (!namespaceNode.isString()) {
return;
}
String namespace = namespaceNode.getString();
if (namespace == null) {
return;
}
if (callee.matchesQualifiedName("goog.require")
|| callee.matchesQualifiedName("goog.forwardDeclare")) {
requires.add(parent);
} else {
if (!requires.isEmpty()) {
t.report(n, PROVIDES_AFTER_REQUIRES);
}
if (callee.matchesQualifiedName("goog.provide")) {
provides.add(parent);
}
}
} else if (NodeUtil.isNameDeclaration(parent.getParent())
&& (callee.matchesQualifiedName("goog.require")
|| callee.matchesQualifiedName("goog.forwardDeclare"))) {
requires.add(parent.getParent());
}
break;
default:
break;
}
}
private void reportIfOutOfOrder(List requiresOrProvides, DiagnosticType warning) {
if (!alphabetical.isOrdered(requiresOrProvides)) {
StringBuilder correctOrder = new StringBuilder();
for (Node n : alphabetical.sortedCopy(requiresOrProvides)) {
correctOrder.append(compiler.toSource(n));
}
compiler.report(
JSError.make(requiresOrProvides.get(0), warning, correctOrder.toString()));
}
}
private void checkForDuplicates(List requires) {
Set namespaces = new HashSet<>();
for (Node require : requires) {
String namespace = getNamespace(require);
if (!namespaces.add(namespace)) {
compiler.report(JSError.make(require, DUPLICATE_REQUIRE, namespace));
}
}
}
private void reset() {
requires.clear();
provides.clear();
}
}