191 lines
6.7 KiB
Python
191 lines
6.7 KiB
Python
import os
|
|
import logging
|
|
import re
|
|
import sys
|
|
|
|
logging.basicConfig(format='%(asctime)s %(name)s: %(message)s',
|
|
level=logging.INFO)
|
|
|
|
logger = logging.getLogger('check-requires')
|
|
|
|
|
|
class Node(object):
|
|
|
|
def __init__(self):
|
|
self.present = False
|
|
self.children = {}
|
|
|
|
def _build_re(self, key):
|
|
if key == '*':
|
|
assert len(self.children) == 0
|
|
# We want to match `.doIt` but not `.SomeClass` or `.more.stuff`
|
|
return '(?=\\.[a-z]\\w*\\b(?!\\.))'
|
|
elif len(self.children) == 1:
|
|
child_key, child = next(self.children.iteritems())
|
|
child_re = child._build_re(child_key)
|
|
if child_key != '*':
|
|
child_re = '\\.' + child_re
|
|
if self.present:
|
|
return key + '(' + child_re + ')?'
|
|
else:
|
|
return key + child_re
|
|
elif self.children:
|
|
children_re = '(?:' + '|'.join(
|
|
('\\.' if k != '*' else '') + self.children[k]._build_re(k)
|
|
for k in sorted(self.children.keys())) + ')'
|
|
if self.present:
|
|
return key + children_re + '?'
|
|
else:
|
|
return key + children_re
|
|
else:
|
|
assert self.present
|
|
return key
|
|
|
|
def build_re(self, key):
|
|
return re.compile('\\b' + self._build_re(key) + '\\b')
|
|
|
|
|
|
def ifind(*paths):
|
|
"""ifind is an iterative version of os.walk, yielding all walked paths and
|
|
normalizing paths to use forward slashes."""
|
|
for path in paths:
|
|
for dirpath, dirnames, names in os.walk(path):
|
|
for name in names:
|
|
if os.sep == '/':
|
|
yield os.path.join(dirpath, name)
|
|
else:
|
|
yield '/'.join(dirpath.split(os.sep) + [name])
|
|
|
|
|
|
def _strip_comments(lines):
|
|
# FIXME this is a horribe hack, we should use a proper JavaScript parser
|
|
# here
|
|
in_multiline_comment = False
|
|
lineno = 0
|
|
for line in lines:
|
|
lineno += 1
|
|
if in_multiline_comment:
|
|
index = line.find('*/')
|
|
if index != -1:
|
|
in_multiline_comment = False
|
|
line = line[index + 2:]
|
|
if not in_multiline_comment:
|
|
line = re.sub(r'//[^\n]*', '', line)
|
|
line = re.sub(r'/\*.*?\*/', '', line)
|
|
index = line.find('/*')
|
|
if index != -1:
|
|
yield lineno, line[:index]
|
|
in_multiline_comment = True
|
|
else:
|
|
yield lineno, line
|
|
|
|
|
|
def check_requires(closure_lib, *filenames):
|
|
unused_count = 0
|
|
all_provides = set()
|
|
|
|
for filename in ifind(closure_lib):
|
|
if filename.endswith('.js'):
|
|
if not re.match(r'.*/closure/goog/', filename):
|
|
continue
|
|
# Skip goog.i18n because it contains so many modules that it causes
|
|
# the generated regular expression to exceed Python's limits
|
|
if re.match(r'.*/closure/goog/i18n/', filename):
|
|
continue
|
|
for line in open(filename, 'rU'):
|
|
m = re.match(r'goog.provide\(\'(.*)\'\);', line)
|
|
if m:
|
|
all_provides.add(m.group(1))
|
|
|
|
for filename in sorted(filenames):
|
|
require_linenos = {}
|
|
uses = set()
|
|
lines = open(filename, 'rU').readlines()
|
|
for lineno, line in _strip_comments(lines):
|
|
m = re.match(r'goog.provide\(\'(.*)\'\);', line)
|
|
if m:
|
|
all_provides.add(m.group(1))
|
|
continue
|
|
m = re.match(r'goog.require\(\'(.*)\'\);', line)
|
|
if m:
|
|
require_linenos[m.group(1)] = lineno
|
|
continue
|
|
ignore_linenos = require_linenos.values()
|
|
for lineno, line in enumerate(lines):
|
|
if lineno in ignore_linenos:
|
|
continue
|
|
for require in require_linenos.iterkeys():
|
|
if require in line:
|
|
uses.add(require)
|
|
for require in sorted(set(require_linenos.keys()) - uses):
|
|
logger.info('%s:%d: unused goog.require: %r' % (
|
|
filename, require_linenos[require], require))
|
|
unused_count += 1
|
|
|
|
all_provides.discard('ol')
|
|
all_provides.discard('ol.MapProperty')
|
|
|
|
root = Node()
|
|
for provide in all_provides:
|
|
node = root
|
|
for component in provide.split('.'):
|
|
if component not in node.children:
|
|
node.children[component] = Node()
|
|
node = node.children[component]
|
|
if component[0].islower():
|
|
# We've arrived at a namespace provide like `ol.foo`.
|
|
# In this case, we want to match uses like `ol.foo.doIt()` but
|
|
# not match things like `new ol.foo.SomeClass()`.
|
|
# For this purpose, we use the special wildcard key for the child.
|
|
node.children['*'] = Node()
|
|
else:
|
|
node.present = True
|
|
provide_res = [child.build_re(key)
|
|
for key, child in root.children.iteritems()]
|
|
missing_count = 0
|
|
for filename in sorted(filenames):
|
|
provides = set()
|
|
requires = set()
|
|
uses = set()
|
|
uses_linenos = {}
|
|
for lineno, line in _strip_comments(open(filename, 'rU')):
|
|
m = re.match(r'goog.provide\(\'(.*)\'\);', line)
|
|
if m:
|
|
provides.add(m.group(1))
|
|
continue
|
|
m = re.match(r'goog.require\(\'(.*)\'\);', line)
|
|
if m:
|
|
requires.add(m.group(1))
|
|
continue
|
|
while True:
|
|
for provide_re in provide_res:
|
|
m = provide_re.search(line)
|
|
if m:
|
|
uses.add(m.group())
|
|
uses_linenos[m.group()] = lineno
|
|
line = line[:m.start()] + line[m.end():]
|
|
break
|
|
else:
|
|
break
|
|
if filename == 'src/ol/renderer/layerrenderer.js':
|
|
uses.discard('ol.renderer.Map')
|
|
m = re.match(
|
|
r'src/ol/renderer/(\w+)/\1(\w*)layerrenderer\.js\Z', filename)
|
|
if m:
|
|
uses.discard('ol.renderer.Map')
|
|
uses.discard('ol.renderer.%s.Map' % (m.group(1),))
|
|
missing_requires = uses - requires - provides
|
|
if missing_requires:
|
|
for missing_require in sorted(missing_requires):
|
|
logger.info("%s:%d missing goog.require('%s')" %
|
|
(filename, uses_linenos[missing_require],
|
|
missing_require))
|
|
missing_count += 1
|
|
if unused_count or missing_count:
|
|
logger.error('%d unused goog.requires, %d missing goog.requires' %
|
|
(unused_count, missing_count))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
check_requires(*sys.argv[1:])
|