[Pkg-javascript-commits] [pdf.js] 02/414: UMD validation and generation tools.

David Prévot taffit at moszumanska.debian.org
Tue Jun 28 17:11:59 UTC 2016


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository pdf.js.

commit 450edc95ccb62d9e48b29a00b199ddb5a09135d0
Author: Yury Delendik <ydelendik at mozilla.com>
Date:   Mon Nov 23 10:58:14 2015 -0600

    UMD validation and generation tools.
---
 external/umdutils/genhtml.js  |  51 +++++
 external/umdutils/verifier.js | 488 ++++++++++++++++++++++++++++++++++++++++++
 make.js                       |   7 +
 3 files changed, 546 insertions(+)

diff --git a/external/umdutils/genhtml.js b/external/umdutils/genhtml.js
new file mode 100644
index 0000000..46b3ee0
--- /dev/null
+++ b/external/umdutils/genhtml.js
@@ -0,0 +1,51 @@
+/* Copyright 2015 Mozilla Foundation
+ *
+ * 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.
+ */
+/* jshint node:true */
+
+'use strict';
+
+// Simple util to re-generate HTML module references in right load order.
+
+var fs = require('fs');
+var path = require('path');
+var umd = require('./verifier.js');
+
+var filePath = process.argv[2];
+if (!filePath) {
+  console.log('USAGE: node ./external/umdutils/genhtml.js <html-file-path>');
+  process.exit(0);
+}
+
+var content = fs.readFileSync(filePath).toString();
+var m, re = /<script\s+src=['"]([^'"]+)/g;
+var filesFound = [];
+while ((m = re.exec(content))) {
+  var jsPath = m[1];
+  if (!/\bsrc\/.*?\.js$/.test(jsPath)) {
+    continue;
+  }
+  filesFound.push(jsPath);
+}
+
+var srcPrefix = filesFound[0].substring(0, filesFound[0].indexOf('src/') + 4);
+
+var dependencies = umd.readDependencies(filesFound.map(function (p) {
+  return path.join(path.dirname(filePath), p);
+}));
+
+dependencies.loadOrder.forEach(function (i) {
+  console.log('<script src="' + i.replace('pdfjs/', srcPrefix) + '.js">'+
+    '</script>');
+});
diff --git a/external/umdutils/verifier.js b/external/umdutils/verifier.js
new file mode 100644
index 0000000..521e4e2
--- /dev/null
+++ b/external/umdutils/verifier.js
@@ -0,0 +1,488 @@
+/* Copyright 2015 Mozilla Foundation
+ *
+ * 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';
+
+/* Utilities for parsing PDF.js UMD file format. A UMD header of the file
+ * shall conform the following rules:
+ *   1. Names of AMD modules and JavaScript object placed to the global object
+ *      shall be alike: symbols'/' and '_' removed, and character case is
+ *      ignored.
+ *   2. CommonJS require shall use relative path to the required module, e.g.
+ *      './display.js' or '../shared/util.js', and they shall construct the
+ *      similar name to AMD one.
+ *   3. Factory function shall contain names for modules, not less than listed
+ *      in AMD, CommonJS or global object properties list, and also their
+ *      names must be alike to name of the root object properties.
+ *
+ * Example:
+ *
+ * (function (root, factory) {
+ *   if (typeof define === 'function' && define.amd) {
+ *     define('pdfjs/display/pattern_helper', ['exports', 'pdfjs/shared/util',
+ *       'pdfjs/display/webgl'], factory);
+ *   } else if (typeof exports !== 'undefined') {
+ *     factory(exports, require('../shared/util.js'), require('./webgl.js'));
+ *   } else {
+ *     factory((root.pdfjsDisplayPatternHelper = {}), root.pdfjsSharedUtil,
+ *       root.pdfjsDisplayWebGL);
+ *   }
+ * }(this, function (exports, sharedUtil, displayWebGL) {
+ *
+ */
+
+var fs = require('fs');
+var path = require('path');
+
+/**
+ * Parses PDF.js UMD header.
+ * @param {string} filePath PDF.js JavaScript file path.
+ * @returns {{amdId: *, amdImports: Array, cjsRequires: Array, jsRootName: *,
+ *   jsImports: Array, imports: Array, importedNames: Array,
+ *   exportedNames: Array, body: *}}
+ */
+function parseUmd(filePath) {
+  var jscode = fs.readFileSync(filePath).toString();
+  // Extracts header and body.
+  var umdStart = '\\(function\\s\\(root,\\sfactory\\)\\s\\{';
+  var umdImports = '\\}\\(this,\\sfunction\\s\\(exports\\b';
+  var umdBody = '\\)\\s\\{';
+  var umdEnd = '\\}\\)\\);\\s*$';
+  var m, re;
+  m = new RegExp(umdStart + '([\\s\\S]*?)' + umdImports + '([\\s\\S]*?)' +
+    umdBody + '([\\s\\S]*?)' + umdEnd).exec(jscode);
+  if (!m) {
+    throw new Error('UMD was not found');
+  }
+  var header = m[1];
+  var imports = m[2].replace(/\s+/g, '').split(',');
+  imports.shift(); // avoiding only-export case
+  var body = m[3];
+
+  // Extracts AMD definitions.
+  var amdMatch = /\bdefine\('([^']*)',\s\[([^\]]*)\],\s+factory\);/.
+    exec(header);
+  if (!amdMatch) {
+    throw new Error('AMD call was not found');
+  }
+  var amdId = amdMatch[1];
+  var amdImports = amdMatch[2].replace(/[\s']+/g, '').split(',');
+  if (amdImports[0] !== 'exports') {
+    throw new Error('exports expected first at AMD call');
+  }
+  amdImports.shift();
+
+  // Extracts CommonJS definitions.
+  var cjsMatch = /\bfactory\(exports((?:,\s+require\([^\)]+\))*)\);/.
+    exec(header);
+  if (!cjsMatch) {
+    throw new Error('CommonJS call was not found');
+  }
+  var cjsRequires = cjsMatch[1].replace(/\s+/g, ' ').trim().
+    replace(/\s*require\('([^']*)'\)/g, '$1').split(',');
+  cjsRequires.shift();
+  var jsMatch = /\bfactory\(\(root\.(\S+)\s=\s\{\}\)((?:,\s+root\.\S+)*)\);/.
+    exec(header);
+  if (!jsMatch) {
+    throw new Error('Regular JS call was not found');
+  }
+
+  // Extracts global object properties definitions.
+  var jsRootName = jsMatch[1];
+  var jsImports = jsMatch[2].replace(/\s+/g, '').split(',');
+  jsImports.shift();
+
+  // Scans for imports usages in the body.
+  var importedNames = [];
+  if (imports.length > 0) {
+    re = new RegExp('\\b(' + imports.join('|') + ')\\.(\\w+)', 'g');
+    while ((m = re.exec(body))) {
+      importedNames.push(m[0]);
+    }
+  }
+  importedNames.sort();
+  for (var i = importedNames.length - 1; i > 0; i--) {
+    if (importedNames[i - 1] === importedNames[i]) {
+      importedNames.splice(i, 1);
+    }
+  }
+  // Scans for exports definitions in the body.
+  var exportedNames = [];
+  re = /\bexports.(\w+)\s*=\s/g;
+  while ((m = re.exec(body))) {
+    exportedNames.push(m[1]);
+  }
+
+  return {
+    amdId: amdId,
+    amdImports: amdImports,
+    cjsRequires: cjsRequires,
+    jsRootName: jsRootName,
+    jsImports: jsImports,
+    imports: imports,
+    importedNames: importedNames,
+    exportedNames: exportedNames,
+    body: body
+  };
+}
+
+/**
+ * Reads and parses all JavaScript root files dependencies and calculates
+ * evaluation/load order.
+ * @param {Array} rootPaths Array of the paths for JavaScript files.
+ * @returns {{modules: null, loadOrder: Array}}
+ */
+function readDependencies(rootPaths) {
+  // Reading of dependencies.
+  var modules = Object.create(null);
+  var processed = Object.create(null);
+  var queue = [];
+  rootPaths.forEach(function (i) {
+    if (processed[i]) {
+      return;
+    }
+    queue.push(i);
+    processed[i] = true;
+  });
+  while (queue.length > 0) {
+    var p = queue.shift();
+    var umd;
+    try {
+      umd = parseUmd(p);
+    } catch (_) {
+      // Ignoring bad UMD modules.
+      continue;
+    }
+    modules[umd.amdId] = {
+      dependencies: umd.amdImports
+    };
+    umd.cjsRequires.forEach(function (r) {
+      if (r[0] !== '.' || !/\.js$/.test(r)) {
+        return; // not pdfjs module
+      }
+      var dependencyPath = path.join(path.dirname(p), r);
+      if (processed[dependencyPath]) {
+        return;
+      }
+      queue.push(dependencyPath);
+      processed[dependencyPath] = true;
+    });
+  }
+
+  // Topological sorting, somewhat Kahn's algorithm but sorts found nodes at
+  // each iteration.
+  processed = Object.create(null);
+  var left = [], result = [];
+  for (var i in modules) {
+    var hasDependencies = modules[i].dependencies.length > 0;
+    if (hasDependencies) {
+      left.push(i);
+    } else {
+      processed[i] = true;
+      result.push(i);
+    }
+  }
+  result.sort();
+  while (left.length > 0) {
+    var discovered = [];
+    left.forEach(function (i) {
+      // Finding if we did not process all dependencies for current module yet.
+      var hasDependecies = modules[i].dependencies.some(function (j) {
+        return !processed[j] && !!modules[j];
+      });
+      if (!hasDependecies) {
+        discovered.push(i);
+      }
+    });
+    if (discovered.length === 0) {
+      throw new Error ('Some circular references exist: somewhere at ' +
+        left.join(','));
+    }
+    discovered.sort();
+    discovered.forEach(function (i) {
+      result.push(i);
+      left.splice(left.indexOf(i), 1);
+      processed[i] = true;
+    });
+  }
+
+  return {modules: modules, loadOrder: result};
+}
+
+/**
+ * Validates individual file. See rules above.
+ */
+function validateFile(path, name, context) {
+  function info(msg) {
+    context.infoCallback(path + ': ' + msg);
+  }
+  function warn(msg) {
+    context.warnCallback(path + ': ' + msg);
+  }
+  function error(msg) {
+    context.errorCallback(path + ': ' + msg);
+  }
+
+  try {
+    var umd = parseUmd(path);
+    info('found ' + umd.amdId);
+
+    if (name !== umd.amdId) {
+      error('AMD name does not match module name');
+    }
+    if (name.replace(/[_\/]/g, '') !== umd.jsRootName.toLowerCase()) {
+      error('root name does not look like module name');
+    }
+
+    if (umd.amdImports.length > umd.imports.length) {
+      error('AMD imports has more entries than body imports');
+    }
+    if (umd.cjsRequires.length > umd.imports.length) {
+      error('CommonJS imports has more entries than body imports');
+    }
+    if (umd.jsImports.length > umd.imports.length) {
+      error('JS imports has more entries than body imports');
+    }
+    var optionalArgs = umd.imports.length - Math.min(umd.amdImports.length,
+        umd.cjsRequires.length, umd.jsImports.length);
+    if (optionalArgs > 0) {
+      warn('' + optionalArgs + ' optional args found: ' +
+        umd.imports.slice(-optionalArgs));
+    }
+    umd.jsImports.forEach(function (i, index) {
+      if (i.indexOf('root.') !== 0) {
+        if (index >= umd.jsImports.length - optionalArgs) {
+          warn('Non-optional non-root based JS import: ' + i);
+        }
+        return;
+      }
+      i = i.substring('root.'.length);
+      var j = umd.imports[index];
+      var offset = i.toLowerCase().lastIndexOf(j.toLowerCase());
+      if (offset + j.length !== i.length) {
+        error('JS import name does not look like corresponding body import ' +
+          'name: ' + i + ' vs ' + j);
+      }
+
+      j = umd.amdImports[index];
+      if (j) {
+        if (j.replace(/[_\/]/g, '') !== i.toLowerCase()) {
+          error('JS import name does not look like corresponding AMD import ' +
+            'name: ' + i + ' vs ' + j);
+        }
+      }
+    });
+    umd.cjsRequires.forEach(function (i, index) {
+      var j = umd.amdImports[index];
+      if (!j) {
+        return; // optional
+      }
+      var noExtension = i.replace(/\.js$/, '');
+      if (noExtension === i || i[0] !== '.') {
+        warn('CommonJS shall have relative path and extension: ' + i);
+        return;
+      }
+      var base = name.split('/');
+      base.pop();
+      var parts = noExtension.split('/');
+      if (parts[0] === '.') {
+        parts.shift();
+      }
+      while (parts[0] === '..') {
+        parts.shift();
+        base.pop();
+      }
+      if (j !== base.concat(parts).join('/')) {
+        error('CommonJS path does not point to right AMD module: ' +
+          i + ' vs ' + j);
+      }
+    });
+
+    umd.imports.forEach(function (i) {
+      var prefix = i + '.';
+      if (umd.importedNames.every(function (j) {
+          return j.indexOf(prefix) !== 0;
+        })) {
+        warn('import is not used to import names: ' + i);
+      }
+    });
+
+    // Recording the module exports and imports for further validation.
+    // See validateImports and validateDependencies below.
+    context.exports[name] = Object.create(null);
+    umd.exportedNames.forEach(function (i) {
+      context.exports[name][i] = true;
+    });
+    context.dependencies[name] = umd.amdImports;
+    umd.importedNames.forEach(function (i) {
+      var parts = i.split('.');
+      var index = umd.imports.indexOf(parts[0]);
+      if (index < 0 || !umd.amdImports[index]) {
+        return; // some optional arg and not in AMD list?
+      }
+      var refModuleName = umd.amdImports[index];
+      var fromModule = context.imports[refModuleName];
+      if (!fromModule) {
+        context.imports[refModuleName] = (fromModule = Object.create(null));
+      }
+      var symbolRefs = fromModule[parts[1]];
+      if (!symbolRefs) {
+        fromModule[parts[1]] = (symbolRefs = []);
+      }
+      symbolRefs.push(name);
+    });
+  } catch (e) {
+    warn(e.message);
+  }
+}
+
+function findFilesInDirectory(dirPath, name, foundFiles) {
+  fs.readdirSync(dirPath).forEach(function (file) {
+    var filePath = dirPath + '/' + file;
+    var stats = fs.statSync(filePath);
+    if (stats.isFile() && /\.js$/i.test(file)) {
+      var fileName = file.substring(0, file.lastIndexOf('.'));
+      foundFiles.push({path: filePath, name: name + '/' + fileName});
+    } else if (stats.isDirectory() && /^\w+$/.test(file)) {
+      findFilesInDirectory(filePath, name + '/' + file, foundFiles);
+    }
+  });
+}
+
+function validateImports(context) {
+  // Checks if some non-exported symbol was imported.
+  for (var i in context.imports) {
+    var exportedSymbols = context.exports[i];
+    if (!exportedSymbols) {
+      context.warnCallback('Exported symbols don\'t exist for: ' + i);
+      continue;
+    }
+    var importedSymbols = context.imports[i];
+    for (var j in importedSymbols) {
+      if (!(j in exportedSymbols)) {
+        context.errorCallback('The non-exported symbol is referred: ' + j +
+          ' from ' + i + ' used in ' + importedSymbols[j]);
+      }
+    }
+  }
+}
+
+function validateDependencies(context) {
+  // Checks for circular dependency (non-efficient algorithm but does the work).
+  var nonRoots = Object.create(null);
+  var i, j, item;
+  for (i in context.dependencies) {
+    var checked = Object.create(null);
+    var queue = [[i]];
+    while (queue.length > 0) {
+      item = queue.shift();
+      j = item[0];
+
+      var dependencies = context.dependencies[j];
+      dependencies.forEach(function (q) {
+        if (!(q in context.dependencies)) {
+          context.warnCallback('Unknown dependency: ' + q);
+          return;
+        }
+
+        var index = item.indexOf(q);
+        if (index >= 0) {
+          context.errorCallback('Circular dependency was found: ' +
+            item.slice(0, index + 1).join('<-'));
+          return;
+        }
+        if (q in checked) {
+          return;
+        }
+        queue.push([q].concat(item));
+        checked[q] = i;
+        nonRoots[q] = true;
+      });
+    }
+  }
+
+  // Some root modules info.
+  for (i in context.dependencies) {
+    if (!(i in nonRoots)) {
+      context.infoCallback('Root module: ' + i);
+    }
+  }}
+
+/**
+ * Validates all modules/files in the specified path. The modules must be
+ * defined using PDF.js UMD format. Results printed to console.
+ * @param {Object} paths The map of the module path prefixes to file/directory
+ *   location.
+ * @param {Object} options (optional) options for validation.
+ * @returns {boolean} true if no error was found.
+ */
+function validateFiles(paths, options) {
+  options = options || {};
+  var verbosity = options.verbosity === undefined ? 0 : options.verbosity;
+  var wasErrorFound = false;
+  var errorCallback = function (msg) {
+    if (verbosity >= 0) {
+      console.error('ERROR:' + msg);
+    }
+    wasErrorFound = true;
+  };
+  var warnCallback = function (msg) {
+    if (verbosity >= 1) {
+      console.warn('WARNING: ' + msg);
+    }
+  };
+  var infoCallback = function (msg) {
+    if (verbosity >= 5) {
+      console.info('INFO: ' + msg);
+    }
+  };
+
+  // Finds all files.
+  for (var name in paths) {
+    if (!paths.hasOwnProperty(name)) {
+      continue;
+    }
+    var path = paths[name];
+    var stats = fs.statSync(path);
+    var foundFiles = [];
+    if (stats.isFile()) {
+      foundFiles.push({path: path, name: name});
+    } else if (stats.isDirectory()) {
+      findFilesInDirectory(path, name, foundFiles);
+    }
+  }
+
+  var context = {
+    exports: Object.create(null),
+    imports: Object.create(null),
+    dependencies: Object.create(null),
+    errorCallback: errorCallback,
+    warnCallback: warnCallback,
+    infoCallback: infoCallback
+  };
+
+  foundFiles.forEach(function (pair) {
+    validateFile(pair.path, pair.name, context);
+  });
+
+  validateImports(context);
+  validateDependencies(context);
+
+  return !wasErrorFound;
+}
+
+exports.parseUmd = parseUmd;
+exports.readDependencies = readDependencies;
+exports.validateFiles = validateFiles;
diff --git a/make.js b/make.js
index 60c360e..6d08c56 100644
--- a/make.js
+++ b/make.js
@@ -1497,6 +1497,13 @@ target.lint = function() {
     exit(1);
   }
 
+  echo();
+  echo('### Checking UMD dependencies');
+  var umd = require('./external/umdutils/verifier.js');
+  if (!umd.validateFiles({'pdfjs': './src'})) {
+    exit(1);
+  }
+
   echo('files checked, no errors found');
 };
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git



More information about the Pkg-javascript-commits mailing list