All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.lint.CheckNestedNames Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2020 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 com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.AbstractCompiler;
import com.google.javascript.jscomp.CompilerPass;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.JSType;
import org.jspecify.nullness.Nullable;

/**
 * Checks if code has a module-level static property assignment (`X.Y`) inside a `goog.module`. If
 * yes, reports a linter warning on the `X.Y` if:
 *
 * 
    *
  1. X is any name {object, class, function, interface, enum, typedef}. *
  2. Y is {class, interface, enum, typedef} *
* *

For example, reports a linter finding in the following: * *

{ @code
 *
 * class C {}
 * let obj = {...};
 *
 * /**
 * * Reports nested name E on class C.
 * * @enum {number}
 * * /
 * C.E={...}; // WARNING:
 *
 * /**
 * * Reports nested interface I on variable obj.
 * * @interface
 * * /
 * obj.I=class {}; // WARNING:
 *
 * // reports nested name D on class C.
 * C.D=class {};
 * }
 * 
* *
{@code
 * // Does not report on nested functions, or any nested property assigned to a value.
 * C.F = function() {}
 * obj.S = '';
 * obj.Some = new Something();
 * }
*/ public final class CheckNestedNames implements CompilerPass, NodeTraversal.Callback { public static final DiagnosticType NESTED_NAME_IN_GOOG_MODULE = DiagnosticType.disabled( "JSC_NESTED_NAME_IN_GOOG_MODULE", "A nested {0} is created on the name `{1}`." + " Fix this linter finding by converting the module-level static property" + " assignment on `{1}` into a module-level flat name (i.e. change `{1}.prop = ...`" + " into `{1}_prop = ...`. You can (if required) export this flat name using named" + " exports (`exports.{1}_prop = {1}_prop`)." ); private final AbstractCompiler compiler; private enum DeclarationKind { CLASS, ENUM, TYPEDEF, INTERFACE, } private static final ImmutableMap NESTED_KIND_TO_REPORT = ImmutableMap.of( DeclarationKind.CLASS, "class", DeclarationKind.ENUM, "enum", DeclarationKind.INTERFACE, "interface", DeclarationKind.TYPEDEF, "typedef"); public CheckNestedNames(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (n.isScript()) { // only traverse inside goog.modules return n.getBooleanProp(Node.GOOG_MODULE); } // only warn on static property assignments of module-level names return nodeTraversal.inGlobalOrModuleScope(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { checkArgument(t.inGlobalOrModuleScope()); if (!n.isGetProp() || !n.isQualifiedName()) { return; } Node targetGetProp = getTargetGetProp(n); if ((parent.isAssign() || parent.isExprResult()) && n.isFirstChildOf(parent) && !isDotPrototype(targetGetProp)) { Node owner = targetGetProp.getFirstChild(); if (!owner.isName() || owner.getString().equals("exports")) { return; // For example `this` or `super` or `exports.SomeEnum = {}` } String ownerName = owner.getString(); DeclarationKind declarationKind = getNestedDeclarationKind(n, parent); if (NESTED_KIND_TO_REPORT.containsKey(declarationKind)) { // Found a nested name that created an inner class, interface, typedef or enum. String nestedKind = NESTED_KIND_TO_REPORT.get(declarationKind); t.report(targetGetProp, NESTED_NAME_IN_GOOG_MODULE, nestedKind, ownerName); } } } /** Gives `a.b` from `a.b.c.d...`. */ private static Node getTargetGetProp(Node n) { checkArgument(n.isGetProp(), n); if (n.getFirstChild().isGetProp()) { return getTargetGetProp(n.getFirstChild()); } return n; } /** True for `someExpression.prototype`. */ private static boolean isDotPrototype(Node getProp) { return getProp.isGetProp() && getProp.getString().equals("prototype"); } private @Nullable DeclarationKind getNestedDeclarationKind(Node lhs, Node parent) { checkArgument(lhs.isFirstChildOf(parent), lhs); // Handle `/** @typedef {...} */` X.Y` or `/** @const */ X.Y = SomeTypeDefName` if (lhs.getTypedefTypeProp() != null) { return DeclarationKind.TYPEDEF; } JSType type = lhs.getJSType(); if (type != null) { // check whether it's an enum, interface or typedef first. if (type.isEnumType()) { return DeclarationKind.ENUM; } else if (type.isInterface()) { return DeclarationKind.INTERFACE; } else if (type.isConstructor()) { return DeclarationKind.CLASS; } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy