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

com.google.javascript.jscomp.js.build_polyfill_table.js 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. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
#!/usr/bin/env node

/*
 * Copyright 2016 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.
 */

'use strict';

const fs = require('fs');

/**
 * Provides an ordering to ensure lower-versioned polyfills don't
 * depend on higher versions.
 */
const ORDER = ['es3', 'es5', 'es6-impl', 'es6'];

/**
 * Prints to stderr and exits.
 * @param {string} message
 */
function fail(message) {
  console.error(message);
  process.exit(1);
}

/**
 * Builds up a table of polyfills.
 */
class PolyfillTable {
  constructor() {
    /** @const {!Map>} */
    this.symbolToFile = new Map();
    /** @const {!Map>} */
    this.deps = new Map();
    /** @const {!Map} */
    this.versions = new Map();
    /** @const {!Array>} */
    this.rows = [];
  }

  /**
   * Returns a shim for $jscomp.polyfill.
   * @param {string} lib Library currently being scanned.
   * @return {function(string, ?Function, string, string)}
   */
  polyfill(lib) {
    return (polyfill, impl, fromLang, toLang) => {
      this.symbolToFile.set(polyfill, this.symbolToFile.get(polyfill) || []);
      this.symbolToFile.get(polyfill).push(lib);
      const row = [polyfill, fromLang, toLang];
      if (impl) {
        row.push(lib);
        this.versions.set(lib, maxVersion(this.versions.get(lib), toLang));
      }
      this.rows.push(row);
    };
  }

  /**
   * Reads a JS file and adds it to the table.
   * @param {string} lib Name of the library.
   * @param {string} data Contents of the file.
   */
  readFile(lib, data) {
    // Look for 'require' directives and add it to the dependency map.
    const deps = new Set();
    this.deps.set(lib, deps);
    const re = /'require ([^']+)'/g;
    let match;
    while (match = re.exec(data)) {
      match[1].split(' ').forEach(dep => deps.add(dep));
    }
    // Now run the file.
    try {
      new Function('$jscomp', data)({
        global: global,
        polyfill: this.polyfill(lib, table),
      });
    } catch (err) {
      throw new Error('Failed to parse file: ' + lib + ': ' + err);
    }
  }

  /**
   * Concatenates the table into a string.  Throws an error if
   * there are any symbols provided by multiple files.
   * @return {string}
   */
  build() {
    const errors = new Set();
    try {
      // First check for duplicate provided symbols.
      for (const entry of this.symbolToFile.entries()) {
        if (entry[1].length != 1) {
          errors.add(
              `ERROR - ${entry[0]} provided by multiple files:${
                  entry[1].map(f => '\n    ' + f).join('')}`);
        }
      }
      // Next ensure all deps have nonincreasing versions.
      checkDeps(errors, this.deps, this.versions);
      // If there are any errors, we should fail; otherwise concatenate.
    } catch (err) {
      errors.add('ERROR - uncaught exception: ' + err.stack);
    }
    if (errors.size) {
      fail(Array.from(errors).join('\n\n'));
    }
    return this.rows.sort().map(row => row.join(' ')).join('\n');
  }
}

/**
 * Checks dependencies for the following issues:
 *   (1) cyclic dependencies
 *   (2) missing dependencies
 *   (3) version mismatches
 * @param {!Set} errors
 * @param {!Map>} deps
 * @param {!Map} versions
 */
function checkDeps(errors, deps, versions) {
  for (const file of deps.keys()) {
    const seen = new Set([file]);
    const queue = [file];
    const version = versions.get(file);
    while (queue.length) {
      const next = queue.shift();
      for (const dep of deps.get(next) || []) {
        if (dep == file) errors.add('ERROR - Cyclic dependency:\n    ' + dep);
        if (seen.has(dep)) continue;
        seen.add(dep);
        queue.push(dep);
        if (!deps.has(dep)) {
          errors.add(
              'ERROR - missing dependency:\n    ' + dep +
              ' required from\n    ' + file);
        }
        const depVersion = versions.get(dep);
        if (version && maxVersion(depVersion, version) != version) {
          errors.add(
              'ERROR - lower version depends on higher version:\n    ' +
              version + ': ' + file + '\n    ' + depVersion + ': ' + dep);
        }
      }
    }
  }
}

/**
 * Returns the higher order of the given versions.
 * @param {string} version1
 * @param {string} version2
 * @return {string} The max version.
 */
function maxVersion(version1, version2) {
  return ORDER[Math.max(ORDER.indexOf(version1), ORDER.indexOf(version2))];
}

const table = new PolyfillTable();

const reads = process.argv.slice(2).map(filename =>
  new Promise((fulfill, reject) =>
    fs.readFile(filename, 'utf8', (err, data) => {
      try {
        if (err) {
          reject(err);
        } else {
          const lib = filename.replace(/^.*?\/js\/|\.js$/g, '');
          table.readFile(lib, data);
          fulfill('');
        }
      } catch (err) {
        reject(err);
      }
    })));

Promise.all(reads).then(
    success => console.log(table.build()),
    failure => fail(failure.stack));




© 2015 - 2024 Weber Informatics LLC | Privacy Policy