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)
#!/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 imp
import os
import tempfile
import unittest
from compiler import Compiler
from processor import FileCache, Processor
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
_SRC_DIR = os.path.join(_SCRIPT_DIR, os.pardir, os.pardir)
_RESOURCES_DIR = os.path.join(_SRC_DIR, "ui", "webui", "resources", "js")
_ASSERT_JS = os.path.join(_RESOURCES_DIR, "assert.js")
_CR_JS = os.path.join(_RESOURCES_DIR, "cr.js")
_CR_UI_JS = os.path.join(_RESOURCES_DIR, "cr", "ui.js")
_PROMISE_RESOLVER_JS = os.path.join(_RESOURCES_DIR, "promise_resolver.js")
_CHROME_EXTERNS = os.path.join(_SRC_DIR, "third_party", "closure_compiler",
"externs", "chrome.js")
_CHROME_SEND_EXTERNS = os.path.join(_SRC_DIR, "third_party", "closure_compiler",
"externs", "chrome_send.js")
_CLOSURE_ARGS_GNI = os.path.join(_SCRIPT_DIR, "closure_args.gni")
_CLOSURE_ARGS = imp.load_source('closure_gni', _CLOSURE_ARGS_GNI)
_COMMON_CLOSURE_ARGS = _CLOSURE_ARGS.default_closure_args + \
_CLOSURE_ARGS.default_disabled_closure_args
class CompilerTest(unittest.TestCase):
_ASSERT_DEFINITION = Processor(_ASSERT_JS).contents
_PROMISE_RESOLVER_DEFINITION = (_ASSERT_DEFINITION +
Processor(_PROMISE_RESOLVER_JS).contents)
_CR_DEFINE_DEFINITION = (_PROMISE_RESOLVER_DEFINITION +
Processor(_CR_JS).contents)
_CR_UI_DECORATE_DEFINITION = Processor(_CR_UI_JS).contents
def setUp(self):
self._compiler = Compiler()
self._tmp_files = []
def tearDown(self):
for file in self._tmp_files:
if os.path.exists(file):
os.remove(file)
def _runCompiler(self, source_code, needs_output, closure_args=None):
file_path = "/script.js"
FileCache._cache[file_path] = source_code
out_file = self._createOutFiles()
args = _COMMON_CLOSURE_ARGS + (closure_args or [])
if needs_output and "checks_only" in args:
args.remove("checks_only")
sources = [file_path, _CHROME_EXTERNS, _CHROME_SEND_EXTERNS]
found_errors, stderr = self._compiler.run(sources,
out_file=out_file,
closure_args=args)
return found_errors, stderr, out_file
def _runCompilerTestExpectError(self, source_code, expected_error,
closure_args=None):
_, stderr, out_file = self._runCompiler(
source_code, needs_output=False, closure_args=closure_args)
self.assertTrue(expected_error in stderr,
msg="Expected chunk: \n%s\n\nOutput:\n%s\n" % (
expected_error, stderr))
self.assertFalse(os.path.exists(out_file))
def _runCompilerTestExpectSuccess(self, source_code, expected_output=None,
closure_args=None):
found_errors, stderr, out_file = self._runCompiler(
source_code, needs_output=True, closure_args=closure_args)
self.assertFalse(found_errors,
msg="Expected success, but got failure\n\nOutput:\n%s\n" % stderr)
self.assertTrue(os.path.exists(out_file))
if expected_output:
with open(out_file, "r") as file:
self.assertEquals(file.read(), expected_output)
def _createOutFiles(self):
out_file = tempfile.NamedTemporaryFile(delete=False)
self._tmp_files.append(out_file.name)
return out_file.name
def testGetInstance(self):
self._runCompilerTestExpectError("""
var cr = {
/** @param {!Function} ctor */
addSingletonGetter: function(ctor) {
ctor.getInstance = function() {
return ctor.instance_ || (ctor.instance_ = new ctor());
};
}
};
/** @constructor */
function Class() {
/** @param {number} num */
this.needsNumber = function(num) {};
}
cr.addSingletonGetter(Class);
Class.getInstance().needsNumber("wrong type");
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of Class.needsNumber does "
"not match formal parameter")
def testCrDefineFunctionDefinition(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() {
/** @param {number} num */
function internalName(num) {}
return {
needsNumber: internalName
};
});
a.b.c.needsNumber("wrong type");
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of a.b.c.needsNumber does "
"not match formal parameter")
def testCrDefineFunctionAssignment(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() {
/** @param {number} num */
var internalName = function(num) {};
return {
needsNumber: internalName
};
});
a.b.c.needsNumber("wrong type");
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of a.b.c.needsNumber does "
"not match formal parameter")
def testCrDefineConstructorDefinitionPrototypeMethod(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() {
/** @constructor */
function ClassInternalName() {}
ClassInternalName.prototype = {
/** @param {number} num */
method: function(num) {}
};
return {
ClassExternalName: ClassInternalName
};
});
new a.b.c.ClassExternalName().method("wrong type");
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of "
"a.b.c.ClassExternalName.prototype.method does not match formal "
"parameter")
def testCrDefineConstructorAssignmentPrototypeMethod(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() {
/** @constructor */
var ClassInternalName = function() {};
ClassInternalName.prototype = {
/** @param {number} num */
method: function(num) {}
};
return {
ClassExternalName: ClassInternalName
};
});
new a.b.c.ClassExternalName().method("wrong type");
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of "
"a.b.c.ClassExternalName.prototype.method does not match formal "
"parameter")
def testCrDefineEnum(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() {
/** @enum {string} */
var internalNameForEnum = {key: 'wrong_type'};
return {
exportedEnum: internalNameForEnum
};
});
/** @param {number} num */
function needsNumber(num) {}
needsNumber(a.b.c.exportedEnum.key);
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of needsNumber does not "
"match formal parameter")
def testObjectDefineProperty(self):
self._runCompilerTestExpectSuccess("""
/** @constructor */
function Class() {}
Object.defineProperty(Class.prototype, 'myProperty', {});
alert(new Class().myProperty);
""")
def testCrDefineProperty(self):
self._runCompilerTestExpectSuccess(self._CR_DEFINE_DEFINITION + """
/** @constructor */
function Class() {}
cr.defineProperty(Class.prototype, 'myProperty', cr.PropertyKind.JS);
alert(new Class().myProperty);
""")
def testCrDefinePropertyTypeChecking(self):
self._runCompilerTestExpectError(self._CR_DEFINE_DEFINITION + """
/** @constructor */
function Class() {}
cr.defineProperty(Class.prototype, 'booleanProp', cr.PropertyKind.BOOL_ATTR);
/** @param {number} num */
function needsNumber(num) {}
needsNumber(new Class().booleanProp);
""", "ERROR - [JSC_TYPE_MISMATCH] actual parameter 1 of needsNumber does not "
"match formal parameter")
def testCrDefineOnCrWorks(self):
self._runCompilerTestExpectSuccess(self._CR_DEFINE_DEFINITION + """
cr.define('cr', function() {
return {};
});
""")
def testAssertWorks(self):
self._runCompilerTestExpectSuccess(self._ASSERT_DEFINITION + """
/** @return {?string} */
function f() {
return "string";
}
/** @type {!string} */
var a = assert(f());
""")
def testAssertInstanceofWorks(self):
self._runCompilerTestExpectSuccess(self._ASSERT_DEFINITION + """
/** @constructor */
function Class() {}
/** @return {Class} */
function f() {
var a = document.createElement('div');
return assertInstanceof(a, Class);
}
""")
def testCrUiDecorateWorks(self):
self._runCompilerTestExpectSuccess(self._CR_DEFINE_DEFINITION +
self._CR_UI_DECORATE_DEFINITION + """
/** @constructor */
function Class() {}
/** @return {Class} */
function f() {
var a = document.createElement('div');
cr.ui.decorate(a, Class);
return a;
}
""")
def testValidScriptCompilation(self):
self._runCompilerTestExpectSuccess("""
var testScript = function() {
console.log("hello world")
};
""",
"""'use strict';var testScript=function(){console.log("hello world")};\n""")
def testOutputWrapper(self):
source_code = """
var testScript = function() {
console.log("hello world");
};
"""
expected_output = ("""(function(){'use strict';var testScript=function()"""
"""{console.log("hello world")};})();\n""")
closure_args=["output_wrapper='(function(){%output%})();'"]
self._runCompilerTestExpectSuccess(source_code, expected_output,
closure_args)
def testCustomSources(self):
source_file1 = tempfile.NamedTemporaryFile(delete=False)
with open(source_file1.name, "w") as f:
f.write("""
var goog;
goog.provide('testScript');
var testScript = function() {};
""")
self._tmp_files.append(source_file1.name)
source_file2 = tempfile.NamedTemporaryFile(delete=False)
with open(source_file2.name, "w") as f:
f.write("""
goog.require('testScript');
testScript();
""")
self._tmp_files.append(source_file2.name)
out_file = self._createOutFiles()
sources = [source_file1.name, source_file2.name]
closure_args = [a for a in _COMMON_CLOSURE_ARGS if a != "checks_only"]
found_errors, stderr = self._compiler.run(sources, out_file=out_file,
closure_args=closure_args,
custom_sources=True)
self.assertFalse(found_errors,
msg="Expected success, but got failure\n\nOutput:\n%s\n" % stderr)
expected_output = "'use strict';var goog,testScript=function(){};testScript();\n"
self.assertTrue(os.path.exists(out_file))
with open(out_file, "r") as file:
self.assertEquals(file.read(), expected_output)
def testMissingReturnAssertNotReached(self):
template = self._ASSERT_DEFINITION + """
/** @enum {number} */
var Enum = {FOO: 1, BAR: 2};
/**
* @param {Enum} e
* @return {number}
*/
function enumToVal(e) {
switch (e) {
case Enum.FOO:
return 1;
case Enum.BAR:
return 2;
}
%s
}
"""
args = ['warning_level=VERBOSE']
self._runCompilerTestExpectError(template % '', 'Missing return',
closure_args=args)
self._runCompilerTestExpectSuccess(template % 'assertNotReached();',
closure_args=args)
if __name__ == "__main__":
unittest.main()
# 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