com.google.javascript.jscomp.WhitelistWarningsGuard.java Source code

Java tutorial

Introduction

Here is the source code for com.google.javascript.jscomp.WhitelistWarningsGuard.java

Source

/*
 * Copyright 2011 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 static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.TreeMultimap;
import com.google.common.io.CharSource;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * An extension of {@code WarningsGuard} that provides functionality to maintain
 * a list of warnings (white-list). It is subclasses' responsibility to decide
 * what to do with the white-list by implementing the {@code level} function.
 * Warnings are defined by the name of the JS file and the first line of
 * warnings description.
 *
 * @author anatol@google.com (Anatol Pomazau)
 * @author bashir@google.com (Bashir Sadjad)
 */
public class WhitelistWarningsGuard extends WarningsGuard {
    private static final Splitter LINE_SPLITTER = Splitter.on('\n');

    /** The set of white-listed warnings, same format as {@code formatWarning}. */
    private final Set<String> whitelist;

    /** Pattern to match line number in error descriptions. */
    private static final Pattern LINE_NUMBER = Pattern.compile(":-?\\d+");

    public WhitelistWarningsGuard() {
        this(ImmutableSet.<String>of());
    }

    /**
     * This class depends on an input set that contains the white-list. The format
     * of each white-list string is:
     * <file-name>:<line-number>?  <warning-description>
     * # <optional-comment>
     *
     * @param whitelist The set of JS-warnings that are white-listed. This is
     *     expected to have similar format as {@code formatWarning(JSError)}.
     */
    public WhitelistWarningsGuard(Set<String> whitelist) {
        Preconditions.checkNotNull(whitelist);
        this.whitelist = normalizeWhitelist(whitelist);
    }

    /**
     * Loads legacy warnings list from the set of strings. During development line
     * numbers are changed very often - we just cut them and compare without ones.
     *
     * @return known legacy warnings without line numbers.
     */
    protected Set<String> normalizeWhitelist(Set<String> whitelist) {
        Set<String> result = new HashSet<>();
        for (String line : whitelist) {
            String trimmed = line.trim();
            if (trimmed.isEmpty() || trimmed.charAt(0) == '#') {
                // strip out empty lines and comments.
                continue;
            }

            // Strip line number for matching.
            result.add(LINE_NUMBER.matcher(trimmed).replaceFirst(":"));
        }
        return ImmutableSet.copyOf(result);
    }

    @Override
    public CheckLevel level(JSError error) {
        if (containWarning(formatWarning(error))) {
            // If the message matches the guard we use WARNING, so that it
            // - Shows up on stderr, and
            // - Gets caught by the WhitelistBuilder downstream in the pipeline
            return CheckLevel.WARNING;
        }

        return null;
    }

    /**
     * Determines whether a given warning is included in the white-list.
     *
     * @param formattedWarning the warning formatted by {@code formatWarning}
     * @return whether the given warning is white-listed or not.
     */
    protected boolean containWarning(String formattedWarning) {
        return whitelist.contains(formattedWarning);
    }

    @Override
    public int getPriority() {
        return WarningsGuard.Priority.SUPPRESS_BY_WHITELIST.getValue();
    }

    /** Creates a warnings guard from a file. */
    public static WhitelistWarningsGuard fromFile(File file) {
        return new WhitelistWarningsGuard(loadWhitelistedJsWarnings(file));
    }

    /**
     * Loads legacy warnings list from the file.
     * @return The lines of the file.
     */
    public static Set<String> loadWhitelistedJsWarnings(File file) {
        return loadWhitelistedJsWarnings(Files.asCharSource(file, UTF_8));
    }

    /**
     * Loads legacy warnings list from the file.
     * @return The lines of the file.
     */
    protected static Set<String> loadWhitelistedJsWarnings(CharSource supplier) {
        try {
            return loadWhitelistedJsWarnings(supplier.openStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Loads legacy warnings list from the file.
     * @return The lines of the file.
     */
    // TODO(nicksantos): This is a weird API.
    static Set<String> loadWhitelistedJsWarnings(Reader reader) throws IOException {
        Preconditions.checkNotNull(reader);
        Set<String> result = new HashSet<>();

        result.addAll(CharStreams.readLines(reader));

        return result;
    }

    /**
     * If subclasses want to modify the formatting, they should override
     * #formatWarning(JSError, boolean), not this method.
     */
    protected String formatWarning(JSError error) {
        return formatWarning(error, false);
    }

    /**
     * @param withMetaData If true, include metadata that's useful to humans
     *     This metadata won't be used for matching the warning.
     */
    protected String formatWarning(JSError error, boolean withMetaData) {
        StringBuilder sb = new StringBuilder();
        sb.append(error.sourceName).append(":");
        if (withMetaData) {
            sb.append(error.lineNumber);
        }
        List<String> lines = LINE_SPLITTER.splitToList(error.description);
        sb.append("  ").append(lines.get(0));

        // Add the rest of the message as a comment.
        if (withMetaData) {
            for (int i = 1; i < lines.size(); i++) {
                sb.append("\n# ").append(lines.get(i));
            }
            sb.append("\n");
        }

        return sb.toString();
    }

    public static String getFirstLine(String warning) {
        int lineLength = warning.indexOf('\n');
        if (lineLength > 0) {
            warning = warning.substring(0, lineLength);
        }
        return warning;
    }

    /** Whitelist builder */
    public class WhitelistBuilder implements ErrorHandler {
        private final Set<JSError> warnings = new LinkedHashSet<>();
        private String productName = null;
        private String generatorTarget = null;
        private String headerNote = null;

        /** Fill in your product name to get a fun message! */
        public WhitelistBuilder setProductName(String name) {
            this.productName = name;
            return this;
        }

        /** Fill in instructions on how to generate this whitelist. */
        public WhitelistBuilder setGeneratorTarget(String name) {
            this.generatorTarget = name;
            return this;
        }

        /** A note to include at the top of the whitelist file. */
        public WhitelistBuilder setNote(String note) {
            this.headerNote = note;
            return this;
        }

        @Override
        public void report(CheckLevel level, JSError error) {
            warnings.add(error);
        }

        /**
         * Writes the warnings collected in a format that the WhitelistWarningsGuard
         * can read back later.
         */
        public void writeWhitelist(File out) throws IOException {
            try (PrintStream stream = new PrintStream(out)) {
                appendWhitelist(stream);
            }
        }

        /**
         * Writes the warnings collected in a format that the WhitelistWarningsGuard
         * can read back later.
         */
        public void appendWhitelist(PrintStream out) {
            out.append("# This is a list of legacy warnings that have yet to be fixed.\n");

            if (productName != null && !productName.isEmpty() && !warnings.isEmpty()) {
                out.append("# Please find some time and fix at least one of them "
                        + "and it will be the happiest day for " + productName + ".\n");
            }

            if (generatorTarget != null && !generatorTarget.isEmpty()) {
                out.append("# When you fix any of these warnings, run " + generatorTarget + " task.\n");
            }

            if (headerNote != null) {
                out.append("#" + Joiner.on("\n# ").join(Splitter.on('\n').split(headerNote)) + "\n");
            }

            Multimap<DiagnosticType, String> warningsByType = TreeMultimap.create();
            for (JSError warning : warnings) {
                warningsByType.put(warning.getType(), formatWarning(warning, true /* withLineNumber */));
            }

            for (DiagnosticType type : warningsByType.keySet()) {
                out.append("\n# Warning ").append(type.key).append(": ")
                        .println(Iterables.get(LINE_SPLITTER.split(type.format.toPattern()), 0));

                for (String warning : warningsByType.get(type)) {
                    out.println(warning);
                }
            }
            out.flush();
        }
    }
}