forked from openlayers/openlayers
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck-requires.py
193 lines (173 loc) · 6.83 KB
/
check-requires.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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
return (unused_count, missing_count)
if __name__ == "__main__":
unused_count, missing_count = check_requires(*sys.argv[1:])
if unused_count > 0 or missing_count > 0:
logger.error('%d unused goog.requires, %d missing goog.requires' %
(unused_count, missing_count))
sys.exit(1)