Commit 04ada830 authored by dpapad's avatar dpapad Committed by Commit Bot

Closure compiler: Remove unused logic from compiler.py.

Specifically removing logic related to handling <if expr> and
<include> tags, which seems to only be triggered by tests, and
nowhere in prod code.

Bug: 1135226
Change-Id: I7d362420e0d94df3c29e6f646cfe25acc5d23e2d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2448658Reviewed-by: default avatarRebekah Potter <rbpotter@chromium.org>
Commit-Queue: dpapad <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815567}
parent 3cfa3b6b
......@@ -6,14 +6,8 @@
"""Runs Closure compiler on JavaScript files to check for errors and produce
minified output."""
import argparse
import os
import re
import subprocess
import sys
import tempfile
import processor
_CURRENT_DIR = os.path.join(os.path.dirname(__file__))
......@@ -34,28 +28,14 @@ class Compiler(object):
"-XX:+TieredCompilation",
]
_POLYMER_EXTERNS = os.path.join(_CURRENT_DIR, "externs", "polymer-1.0.js")
def __init__(self, verbose=False):
"""
Args:
verbose: Whether this class should output diagnostic messages.
"""
self._compiler_jar = os.path.join(_CURRENT_DIR, "compiler", "compiler.jar")
self._target = None
self._temp_files = []
self._verbose = verbose
def _nuke_temp_files(self):
"""Deletes any temp files this class knows about."""
if not self._temp_files:
return
self._log_debug("Deleting temp files: %s" % ", ".join(self._temp_files))
for f in self._temp_files:
os.remove(f)
self._temp_files = []
def _log_debug(self, msg, error=False):
"""Logs |msg| to stdout if --verbose/-v is passed when invoking this script.
......@@ -65,14 +45,6 @@ class Compiler(object):
if self._verbose:
print "(INFO) %s" % msg
def _log_error(self, msg):
"""Logs |msg| to stderr regardless of --flags.
Args:
msg: An error message to log.
"""
print >> sys.stderr, "(ERROR) %s" % msg
def run_jar(self, jar, args):
"""Runs a .jar from the command line with arguments.
......@@ -92,223 +64,3 @@ class Compiler(object):
process = subprocess.Popen(shell_command, **kwargs)
_, stderr = process.communicate()
return process.returncode, stderr
def _get_line_number(self, match):
"""When chrome is built, it preprocesses its JavaScript from:
<include src="blah.js">
alert(1);
to:
/* contents of blah.js inlined */
alert(1);
Because Closure Compiler requires this inlining already be done (as
<include> isn't valid JavaScript), this script creates temporary files to
expand all the <include>s.
When type errors are hit in temporary files, a developer doesn't know the
original source location to fix. This method maps from /tmp/file:300 back to
/original/source/file:100 so fixing errors is faster for developers.
Args:
match: A re.MatchObject from matching against a line number regex.
Returns:
The fixed up /file and :line number.
"""
real_file = self._processor.get_file_from_line(match.group(1))
return "%s:%d" % (os.path.abspath(real_file.file), real_file.line_number)
def _clean_up_error(self, error):
"""Reverse the effects that funky <include> preprocessing steps have on
errors messages.
Args:
error: A Closure compiler error (2 line string with error and source).
Return:
The fixed up error string.
"""
assert self._target
assert self._expanded_file
expanded_file = self._expanded_file
fixed = re.sub("%s:(\d+)" % expanded_file, self._get_line_number, error)
return fixed.replace(expanded_file, os.path.abspath(self._target))
def _format_errors(self, errors):
"""Formats Closure compiler errors to easily spot compiler output.
Args:
errors: A list of strings extracted from the Closure compiler's output.
Returns:
A formatted output string.
"""
contents = "\n## ".join("\n\n".join(errors).splitlines())
return "## %s" % contents if contents else ""
def _create_temp_file(self, contents):
"""Creates an owned temporary file with |contents|.
Args:
content: A string of the file contens to write to a temporary file.
Return:
The filepath of the newly created, written, and closed temporary file.
"""
with tempfile.NamedTemporaryFile(mode="wt", delete=False) as tmp_file:
self._temp_files.append(tmp_file.name)
tmp_file.write(contents)
return tmp_file.name
def run(self, sources, out_file, closure_args=None,
custom_sources=False, custom_includes=False):
"""Closure compile |sources| while checking for errors.
Args:
sources: Files to compile. sources[0] is the typically the target file.
sources[1:] are externs and dependencies in topological order. Order
is not guaranteed if custom_sources is True.
out_file: A file where the compiled output is written to.
closure_args: Arguments passed directly to the Closure compiler.
custom_sources: Whether |sources| was customized by the target (e.g. not
in GYP dependency order).
custom_includes: Whether <include>s are processed when |custom_sources|
is True.
Returns:
(found_errors, stderr) A boolean indicating whether errors were found and
the raw Closure compiler stderr (as a string).
"""
is_extern = lambda f: 'externs' in f
externs_and_deps = [self._POLYMER_EXTERNS]
if custom_sources:
if custom_includes:
# TODO(dbeam): this is fairly hacky. Can we just remove custom_sources
# soon when all the things kept on life support using it die?
self._target = sources.pop()
externs_and_deps += sources
else:
self._target = sources[0]
externs_and_deps += sources[1:]
externs = filter(is_extern, externs_and_deps)
deps = filter(lambda f: not is_extern(f), externs_and_deps)
assert externs or deps or self._target
self._log_debug("Externs: %s" % externs)
self._log_debug("Dependencies: %s" % deps)
self._log_debug("Target: %s" % self._target)
js_args = deps + ([self._target] if self._target else [])
process_includes = custom_includes or not custom_sources
if process_includes:
# TODO(dbeam): compiler.jar automatically detects "@externs" in a --js arg
# and moves these files to a different AST tree. However, because we use
# one big funky <include> meta-file, it thinks all the code is one big
# externs. Just use --js when <include> dies.
cwd, tmp_dir = os.getcwd(), tempfile.gettempdir()
rel_path = lambda f: os.path.join(os.path.relpath(cwd, tmp_dir), f)
contents = ['<include src="%s">' % rel_path(f) for f in js_args]
meta_file = self._create_temp_file("\n".join(contents))
self._log_debug("Meta file: %s" % meta_file)
self._processor = processor.Processor(meta_file)
self._expanded_file = self._create_temp_file(self._processor.contents)
self._log_debug("Expanded file: %s" % self._expanded_file)
js_args = [self._expanded_file]
closure_args = closure_args or []
closure_args += ["summary_detail_level=3", "continue_after_errors"]
args = ["--externs=%s" % e for e in externs] + \
["--js=%s" % s for s in js_args] + \
["--%s" % arg for arg in closure_args]
assert out_file
out_dir = os.path.dirname(out_file)
if not os.path.exists(out_dir):
os.makedirs(out_dir)
checks_only = 'checks_only' in closure_args
if not checks_only:
args += ["--js_output_file=%s" % out_file]
self._log_debug("Args: %s" % " ".join(args))
return_code, stderr = self.run_jar(self._compiler_jar, args)
errors = stderr.strip().split("\n\n")
maybe_summary = errors.pop()
summary = re.search("(?P<error_count>\d+).*error.*warning", maybe_summary)
if summary:
self._log_debug("Summary: %s" % maybe_summary)
else:
# Not a summary. Running the jar failed. Bail.
self._log_error(stderr)
self._nuke_temp_files()
sys.exit(1)
if summary.group('error_count') != "0":
if os.path.exists(out_file):
os.remove(out_file)
elif checks_only and return_code == 0:
# Compile succeeded but --checks_only disables --js_output_file from
# actually writing a file. Write a file ourselves so incremental builds
# still work.
with open(out_file, 'w') as f:
f.write('')
if process_includes:
errors = map(self._clean_up_error, errors)
output = self._format_errors(errors)
if errors:
prefix = "\n" if output else ""
self._log_error("Error in: %s%s%s" % (self._target, prefix, output))
elif output:
self._log_debug("Output: %s" % output)
self._nuke_temp_files()
return bool(errors) or return_code > 0, stderr
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Typecheck JavaScript using Closure compiler")
parser.add_argument("sources", nargs=argparse.ONE_OR_MORE,
help="Path to a source file to typecheck")
parser.add_argument("--custom_sources", action="store_true",
help="Whether this rules has custom sources.")
parser.add_argument("--custom_includes", action="store_true",
help="If present, <include>s are processed when "
"using --custom_sources.")
parser.add_argument("-o", "--out_file", required=True,
help="A file where the compiled output is written to")
parser.add_argument("-c", "--closure_args", nargs=argparse.ZERO_OR_MORE,
help="Arguments passed directly to the Closure compiler")
parser.add_argument("-v", "--verbose", action="store_true",
help="Show more information as this script runs")
opts = parser.parse_args()
compiler = Compiler(verbose=opts.verbose)
found_errors, stderr = compiler.run(opts.sources, out_file=opts.out_file,
closure_args=opts.closure_args,
custom_sources=opts.custom_sources,
custom_includes=opts.custom_includes)
if found_errors:
if opts.custom_sources:
print stderr
sys.exit(1)
This diff is collapsed.
# 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.
"""Process Chrome resources (HTML/CSS/JS) to handle <include> and <if> tags."""
from collections import defaultdict
import re
import os
class LineNumber(object):
"""A simple wrapper to hold line information (e.g. file.js:32)."""
def __init__(self, source_file, line_number):
"""
Args:
source_file: A file path (as a string).
line_number: The line in |file| (as an integer).
"""
self.file = source_file
self.line_number = int(line_number)
class FileCache(object):
"""An in-memory cache to speed up reading the same files over and over.
Usage:
FileCache.read(path_to_file)
"""
_cache = defaultdict(str)
@classmethod
def read(self, source_file):
"""Read a file and return it as a string.
Args:
source_file: a file path (as a string) to read and return the contents.
Returns:
The contents of |source_file| (as a string).
"""
abs_file = os.path.abspath(source_file)
self._cache[abs_file] = self._cache[abs_file] or open(abs_file, "r").read()
return self._cache[abs_file]
class Processor(object):
"""Processes resource files, inlining the contents of <include> tags, removing
<if> tags, and retaining original line info.
For example
1: /* blah.js */
2: <if expr="is_win">
3: <include src="win.js">
4: </if>
would be turned into:
1: /* blah.js */
2:
3: /* win.js */
4: alert('Ew; Windows.');
5:
"""
_IF_TAGS_REG = "</?if[^>]*?>"
_INCLUDE_REG = "<include[^>]+src=['\"]([^>]*)['\"]>"
def __init__(self, source_file):
"""
Args:
source_file: A file path to process (as a string).
"""
self.included_files = set()
self._index = 0
self._lines = self._get_file(source_file)
# Can't enumerate(self._lines) here because some lines are re-processed.
while self._index < len(self._lines):
current_line = self._lines[self._index]
match = re.search(self._INCLUDE_REG, current_line[2])
if match:
file_dir = os.path.dirname(current_line[0])
file_name = os.path.abspath(os.path.join(file_dir, match.group(1)))
if file_name not in self.included_files:
self._include_file(file_name)
continue # Stay on the same line.
else:
# Found a duplicate <include>. Ignore and insert a blank line to
# preserve line numbers.
self._lines[self._index] = self._lines[self._index][:2] + ("",)
self._index += 1
for i, line in enumerate(self._lines):
self._lines[i] = line[:2] + (re.sub(self._IF_TAGS_REG, "", line[2]),)
self.contents = "\n".join(l[2] for l in self._lines)
# Returns a list of tuples in the format: (file, line number, line contents).
def _get_file(self, source_file):
lines = FileCache.read(source_file).splitlines()
return [(source_file, lnum + 1, line) for lnum, line in enumerate(lines)]
def _include_file(self, source_file):
self.included_files.add(source_file)
f = self._get_file(source_file)
self._lines = self._lines[:self._index] + f + self._lines[self._index + 1:]
def get_file_from_line(self, line_number):
"""Get the original file and line number for an expanded file's line number.
Args:
line_number: A processed file's line number (as an integer or string).
"""
line_number = int(line_number) - 1
return LineNumber(self._lines[line_number][0], self._lines[line_number][1])
#!/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.
"""Test resources processing, i.e. <if> and <include> tag handling."""
import unittest
from processor import FileCache, Processor, LineNumber
class ProcessorTest(unittest.TestCase):
"""Test <include> tag processing logic."""
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.maxDiff = None
def setUp(self):
FileCache._cache["/debug.js"] = """
// Copyright 2002 Older Chromium Author dudes.
function debug(msg) { if (window.DEBUG) alert(msg); }
""".strip()
FileCache._cache["/global.js"] = """
// Copyright 2014 Old Chromium Author dudes.
<include src="/debug.js">
var global = 'type checking!';
""".strip()
FileCache._cache["/checked.js"] = """
// Copyright 2028 Future Chromium Author dudes.
/**
* @fileoverview Coolest app ever.
* @author Douglas Crockford (douglas@crockford.com)
*/
<include src="/global.js">
debug(global);
// Here continues checked.js, a swell file.
""".strip()
FileCache._cache["/double-debug.js"] = """
<include src="/debug.js">
<include src="/debug.js">
""".strip()
self._processor = Processor("/checked.js")
def testInline(self):
self.assertMultiLineEqual("""
// Copyright 2028 Future Chromium Author dudes.
/**
* @fileoverview Coolest app ever.
* @author Douglas Crockford (douglas@crockford.com)
*/
// Copyright 2014 Old Chromium Author dudes.
// Copyright 2002 Older Chromium Author dudes.
function debug(msg) { if (window.DEBUG) alert(msg); }
var global = 'type checking!';
debug(global);
// Here continues checked.js, a swell file.
""".strip(), self._processor.contents)
def assertLineNumber(self, abs_line, expected_line):
actual_line = self._processor.get_file_from_line(abs_line)
self.assertEqual(expected_line.file, actual_line.file)
self.assertEqual(expected_line.line_number, actual_line.line_number)
def testGetFileFromLine(self):
"""Verify that inlined files retain their original line info."""
self.assertLineNumber(1, LineNumber("/checked.js", 1))
self.assertLineNumber(5, LineNumber("/checked.js", 5))
self.assertLineNumber(6, LineNumber("/global.js", 1))
self.assertLineNumber(7, LineNumber("/debug.js", 1))
self.assertLineNumber(8, LineNumber("/debug.js", 2))
self.assertLineNumber(9, LineNumber("/global.js", 3))
self.assertLineNumber(10, LineNumber("/checked.js", 7))
self.assertLineNumber(11, LineNumber("/checked.js", 8))
def testIncludedFiles(self):
"""Verify that files are tracked correctly as they're inlined."""
self.assertEquals(set(["/global.js", "/debug.js"]),
self._processor.included_files)
def testDoubleIncludedSkipped(self):
"""Verify that doubly included files are skipped."""
processor = Processor("/double-debug.js")
self.assertEquals(set(["/debug.js"]), processor.included_files)
self.assertEquals(FileCache.read("/debug.js") + "\n", processor.contents)
class IfStrippingTest(unittest.TestCase):
"""Test that the contents of XML <if> blocks are stripped."""
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
self.maxDiff = None
def setUp(self):
FileCache._cache["/century.js"] = """
function getCurrentCentury() {
<if expr="netscape_os">
alert("Oh wow!");
return "XX";
</if>
return "XXI";
}
""".strip()
self.processor_ = Processor("/century.js")
def testIfStripping(self):
self.assertMultiLineEqual("""
function getCurrentCentury() {
alert("Oh wow!");
return "XX";
return "XXI";
}
""".strip(), self.processor_.contents)
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
# Copyright 2015 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 compiler_test
import processor_test
for test_module in [compiler_test, processor_test]:
test_module.unittest.main(test_module)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment