
com.google.javascript.jscomp.lint.CheckRequiresSorted Maven / Gradle / Ivy
Show all versions of closure-compiler-unshaded Show documentation
/*
* Copyright 2019 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.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.errorprone.annotations.Immutable;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
/**
* Checks that Closure import statements (goog.require, goog.requireType, and goog.forwardDeclare)
* are sorted and deduplicated, exposing the necessary information to produce a suggested fix.
*/
public final class CheckRequiresSorted implements NodeTraversal.Callback {
public static final DiagnosticType REQUIRES_NOT_SORTED =
DiagnosticType.warning(
"JSC_REQUIRES_NOT_SORTED",
"goog.require() and goog.requireType() statements are not in recommended format."
+ " The correct order is:\n\n{0}\n");
/** Operation modes. */
public enum Mode {
/** Collect information to determine whether a fix is required, but do not report a warning. */
COLLECT_ONLY,
/** Additionally report a warning. */
COLLECT_AND_REPORT
};
/** Primitives that may be called in an import statement. */
enum ImportPrimitive {
REQUIRE("goog.require"),
REQUIRE_TYPE("goog.requireType"),
FORWARD_DECLARE("goog.forwardDeclare");
private final String name;
private ImportPrimitive(String name) {
this.name = name;
}
/** Returns the primitive with the given name. */
static ImportPrimitive fromName(String name) {
for (ImportPrimitive primitive : values()) {
if (primitive.name.equals(name)) {
return primitive;
}
}
throw new IllegalArgumentException("Invalid primitive name " + name);
}
static final ImportPrimitive WEAKEST = FORWARD_DECLARE;
/**
* Returns the stronger of two primitives.
*
* `goog.require` is stronger than `goog.requireType`, which is stronger than
* `goog.forwardDeclare`.
*/
@Nullable
static ImportPrimitive stronger(ImportPrimitive p1, ImportPrimitive p2) {
return p1.ordinal() < p2.ordinal() ? p1 : p2;
}
@Override
public String toString() {
return name;
}
}
/**
* One of the bindings of a destructuring pattern.
*
*
{@code exportedName} and {@code localName} are equal in the case where the binding does not
* explicitly specify a local name.
*/
@AutoValue
@Immutable
abstract static class DestructuringBinding implements Comparable {
abstract String exportedName();
abstract String localName();
abstract boolean isShorthandProperty();
static DestructuringBinding of(
String exportedName, String localName, boolean isShorthandProperty) {
checkArgument(!isShorthandProperty || exportedName.equals(localName));
return new AutoValue_CheckRequiresSorted_DestructuringBinding(
exportedName, localName, isShorthandProperty);
}
/** Compares two bindings according to the style guide sort order. */
@Override
public int compareTo(DestructuringBinding other) {
return ComparisonChain.start()
.compare(this.exportedName(), other.exportedName())
.compare(this.localName(), other.localName())
.result();
}
/**
* Returns true if the destructuring binding is not canonical.
*
*
For example:
*
`{Foo}` is canonical
* `{Foo: Bar}` is canonical
*
`{Foo: Foo}` is not canonical
*/
private boolean isCanonical() {
return !this.exportedName().equals(this.localName()) || this.isShorthandProperty();
}
/**
* Canonicalizes the destructuring to a shorthand property when applicable.
*
* In practice, `{Foo: Foo}` gets simplified to `{Foo}`.
*/
public DestructuringBinding canonicalizeShorthandProperties() {
return this.isCanonical()
? this
: DestructuringBinding.of(
this.exportedName(), this.localName(), /* isShorthandProperty= */ true);
}
}
/**
* An import statement, which may have been merged from several import statements for the same
* namespace in the original code.
*
*
An import statement has exactly one of three shapes:
*
*
* - Standalone: has no LHS, as in `goog.require('namespace')`.
*
- Aliasing: has an LHS with an alias, as in `const alias = goog.require('namespace')`.
*
- Destructuring: has an LHS with a destructuring pattern, as in `const {name: localName} =
* goog.require('namespace')`.
*
*/
@AutoValue
abstract static class ImportStatement implements Comparable {
/** Returns the nodes this import statement was merged from, in source order. */
abstract ImmutableList nodes();
/** Returns the import primitive being called. */
abstract ImportPrimitive primitive();
/** Returns the namespace being imported. */
abstract String namespace();
/** Returns the alias for an aliasing import, or null if the import isn't aliasing. */
abstract @Nullable String alias();
/**
* Returns the destructures for a destructuring import in source order, or null if the import
* isn't destructuring.
*
*
If the import is destructuring but the pattern is empty, the value is non-null but empty.
*/
abstract ImmutableList