blob: 32aa3b4c60c12d11b11c4328a762ff52fcd2176c [file] [log] [blame]
# Copyright 2016 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.
"""Identifies Polymer elements that downloaded but not used by Chrome.
Only finds "first-order" unused elements; re-run after removing unused elements
to check if other elements have become unused.
"""
import os
import re
import subprocess
import sys
_HERE_PATH = os.path.dirname(__file__)
_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..'))
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
import node
import node_modules
class UnusedElementsDetector(object):
"""Finds unused Polymer elements."""
# Unused elements to ignore because we plan to use them soon.
__WHITELIST = (
# Necessary for closure.
'polymer-externs',
# Not used yet. Will be used as part of Polymer 2 migration.
'polymer2',
'shadycss',
# Not used yet. Will be used when pages are moved off of HTML imports.
'html-imports',
'html-imports-v0',
)
def __init__(self):
polymer_dir = os.path.dirname(os.path.realpath(__file__))
self.__COMPONENTS_DIR = os.path.join(polymer_dir, 'components-chromium')
@staticmethod
def __StripHtmlComments(filename):
"""Returns the contents of an HTML file with <!-- --> comments stripped.
Not a real parser.
Args:
filename: The name of the file to read.
Returns:
A string consisting of the file contents with comments removed.
"""
with open(filename) as f:
return re.sub('<!--.*?-->', '', f.read(), flags=re.MULTILINE | re.DOTALL)
@staticmethod
def __StripJsComments(filename):
"""Returns the minified contents of a JavaScript file with comments and
grit directives removed.
Args:
filename: The name of the file to read.
Returns:
A string consisting of the minified file contents with comments and grit
directives removed.
"""
with open(filename) as f:
text = f.read()
text = re.sub('<if .*?>', '', text, flags=re.IGNORECASE)
text = re.sub('</if>', '', text, flags=re.IGNORECASE)
return node.RunNode([node_modules.PathToUglify(), filename])
@staticmethod
def __StripComments(filename):
"""Returns the contents of a JavaScript or HTML file with comments removed.
Args:
filename: The name of the file to read.
Returns:
A string consisting of the file contents processed via
__StripHtmlComments or __StripJsComments.
"""
if filename.endswith('.html'):
text = UnusedElementsDetector.__StripHtmlComments(filename)
elif filename.endswith('.js'):
text = UnusedElementsDetector.__StripJsComments(filename)
else:
assert False, 'Invalid filename: %s' % filename
return text
def Run(self):
"""Finds unused Polymer elements and prints a summary."""
proc = subprocess.Popen(
['git', 'rev-parse', '--show-toplevel'],
stdout=subprocess.PIPE)
src_dir = proc.stdout.read().strip()
elements = []
for name in os.listdir(self.__COMPONENTS_DIR):
path = os.path.join(self.__COMPONENTS_DIR, name)
if os.path.isdir(path):
elements.append(name)
relevant_src_dirs = (
os.path.join(src_dir, 'chrome'),
os.path.join(src_dir, 'ui'),
os.path.join(src_dir, 'components'),
self.__COMPONENTS_DIR
)
unused_elements = []
for element in elements:
if (element not in self.__WHITELIST and
not self.__IsImported(element, relevant_src_dirs)):
unused_elements.append(element)
if unused_elements:
print 'Found unused elements: %s\nRemove from bower.json and re-run ' \
'reproduce.sh, or add to whitelist in %s' % (
', '.join(unused_elements), os.path.basename(__file__))
def __IsImported(self, element_dir, dirs):
"""Returns whether the element directory is used in HTML or JavaScript.
Args:
element_dir: The name of the element's directory.
dirs: The directories in which to check for usage.
Returns:
True if the element's directory is used in |dirs|.
"""
for path in dirs:
# Find an import or script referencing the tag's directory.
for (dirpath, _, filenames) in os.walk(path):
# Ignore the element's own files.
if dirpath.startswith(os.path.join(
self.__COMPONENTS_DIR, element_dir)):
continue
for filename in filenames:
if not filename.endswith('.html') and not filename.endswith('.js'):
continue
with open(os.path.join(dirpath, filename)) as f:
text = f.read()
if not re.search('/%s/' % element_dir, text):
continue
# Check the file again, ignoring comments (e.g. example imports and
# scripts).
if re.search('/%s' % element_dir,
self.__StripComments(
os.path.join(dirpath, filename))):
return True
return False
if __name__ == '__main__':
UnusedElementsDetector().Run()