blob: b81f486615c58e479d5e95f414bb4689d4dcd139 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
from checker import Checker as Checker
import os
import sys
try:
import json
except:
import simplejson as json
class Module(object):
def __init__(self, name, sources, depends=[], externs=[]):
self.name = name
self.sources = sources
# TODO(dbeam): support depending on other modules/dependency flattening.
self.depends = depends
self.externs = externs
@staticmethod
def from_dict(d):
keys = d.keys()
required = ["name", "sources"]
assert all(r in keys for r in required), "Module missing name or sources"
allowed = required + ["depends", "externs"]
assert all(k in allowed for k in keys), "Module has unknown key"
depends = d["depends"] if "depends" in d else []
externs = d["externs"] if "externs" in d else []
return Module(d["name"], d["sources"], depends=depends, externs=externs)
# TODO(dbeam): should ModuleParser be internal to ModuleCompiler or should we
# pass Modules into ModuleCompiler.compile()? Maybe this is fine?
class ModuleParser(object):
_cache = {}
def __init__(self, verbose=False):
self._verbose = verbose
def parse(self, file_path):
if file_path in self._cache:
print "(INFO) Found module file %s in the cache" % file_path
return self._cache[file_path]
file = open(file_path, "r")
data = json.load(file)
file.close()
if self._verbose:
pretty_json = json.dumps(data, indent=2, separators=(',', ': ')).strip()
print "(INFO) Layout: " + os.linesep + pretty_json + os.linesep
self._cache[file_path] = [Module.from_dict(m) for m in data]
return self._cache[file_path]
class ModuleCompiler(object):
_checker = None
_parser = None
def __init__(self, verbose=False):
self._verbose = verbose
def _debug(self, msg, prefix="(INFO) ", suffix=""):
if self._verbose:
print prefix + msg.strip() + suffix
def compile(self, module_file):
self._debug("MODULE FILE: " + module_file, prefix="")
# NOTE: It's possible but unlikely that |_checker| or |_parser|'s verbosity
# isn't the same as |self._verbose| due to this class being called with
# verbose=False then verbose=True in the same program.
self._parser = self._parser or ModuleParser(verbose=self._verbose)
self._checker = self._checker or Checker(verbose=self._verbose)
current_dir = os.getcwd()
module_dir = os.path.dirname(module_file)
rel_path = lambda f: f
if current_dir and module_dir:
here_to_module_dir = os.path.relpath(module_dir, current_dir)
if here_to_module_dir:
rel_path = lambda f: os.path.join(here_to_module_dir, f)
modules = self._parser.parse(module_file)
for m in modules:
self._debug("MODULE: " + m.name, prefix="", suffix=os.linesep)
for s in m.sources:
depends = [rel_path(d) for d in m.depends]
externs = [rel_path(e) for e in m.externs]
exit_code, _ = self._checker.check(rel_path(s), depends=depends,
externs=externs)
if exit_code:
sys.exit(exit_code)
if s != m.sources[-1]:
self._debug(os.linesep, prefix="")
if m != modules[-1]:
self._debug(os.linesep, prefix="")
def main(opts):
module_compiler = ModuleCompiler(verbose=opts.verbose)
for module_file in opts.module_file:
module_compiler.compile(module_file)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Typecheck JavaScript using Closure compiler")
parser.add_argument("-v", "--verbose", action="store_true",
help="Show more information as this script runs")
parser.add_argument("module_file", nargs=argparse.ONE_OR_MORE,
help="Path to a modules file to check")
main(parser.parse_args())