Commit 15b0e246 authored by vitalyp@chromium.org's avatar vitalyp@chromium.org

Handle property definition by {cr|Object}.defineProperty() in compiler pass

BUG=393873
R=dbeam@chromium.org,tbreisacher@chromium.org
TEST=third_party/closure_compiler/runner/how_to_test_compiler_pass.md

Review URL: https://codereview.chromium.org/460163002

Cr-Commit-Position: refs/heads/master@{#289535}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@289535 0039d316-1c4b-4281-b951-d872f2087c98
parent a450d868
...@@ -18,6 +18,7 @@ Local modifications: ...@@ -18,6 +18,7 @@ Local modifications:
compiler to "IDE mode" (single-file checks, doesn't stop on first error). compiler to "IDE mode" (single-file checks, doesn't stop on first error).
- Chrome-specific coding conventions to understand cr.addSingletonGetter(). - Chrome-specific coding conventions to understand cr.addSingletonGetter().
- third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java - third_party/closure_compiler/runner/src/com/google/javascript/jscomp/ChromePass.java
Added pass to handle Chrome-specific namespace creation with cr.define() Added pass to handle namespace definition with cr.define() and property
definition with {cr|Object}.defineProperty().
See third_party/closure_compiler/runner/how_to_test_compiler_pass.md for See third_party/closure_compiler/runner/how_to_test_compiler_pass.md for
testing instructions on this pass. testing instructions on this pass.
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
import os import os
import unittest import unittest
from checker import Checker, FileCache, Flattener from checker import Checker
from processor import FileCache, Processor
CR_FILE = os.path.join("..", "..", "ui", "webui", "resources", "js", "cr.js") CR_FILE = os.path.join("..", "..", "ui", "webui", "resources", "js", "cr.js")
...@@ -18,22 +19,32 @@ def rel_to_abs(rel_path): ...@@ -18,22 +19,32 @@ def rel_to_abs(rel_path):
class CompilerCustomizationTest(unittest.TestCase): class CompilerCustomizationTest(unittest.TestCase):
_CR_DEFINE_DEFINITION = Flattener(rel_to_abs(CR_FILE)).contents _CR_DEFINE_DEFINITION = Processor(rel_to_abs(CR_FILE)).contents
def setUp(self): def setUp(self):
self._checker = Checker() self._checker = Checker()
def _runCheckerTest(self, source_code, expected_error): def _runChecker(self, source_code):
file_path = "/script.js" file_path = "/script.js"
FileCache._cache[file_path] = source_code FileCache._cache[file_path] = source_code
_, output = self._checker.check(file_path) return self._checker.check(file_path)
def _runCheckerTestExpectError(self, source_code, expected_error):
_, output = self._runChecker(source_code)
self.assertTrue(expected_error in output, self.assertTrue(expected_error in output,
msg="Expected chunk: \n%s\n\nOutput:\n%s\n" % ( msg="Expected chunk: \n%s\n\nOutput:\n%s\n" % (
expected_error, output)) expected_error, output))
def _runCheckerTestExpectSuccess(self, source_code):
return_code, output = self._runChecker(source_code)
self.assertTrue(return_code == 0,
msg="Expected success, got return code %d\n\nOutput:\n%s\n" % (
return_code, output))
def testGetInstance(self): def testGetInstance(self):
self._runCheckerTest(source_code=""" self._runCheckerTestExpectError("""
var cr = { var cr = {
/** @param {!Function} ctor */ /** @param {!Function} ctor */
addSingletonGetter: function(ctor) { addSingletonGetter: function(ctor) {
...@@ -51,12 +62,11 @@ function Class() { ...@@ -51,12 +62,11 @@ function Class() {
cr.addSingletonGetter(Class); cr.addSingletonGetter(Class);
Class.getInstance().needsNumber("wrong type"); Class.getInstance().needsNumber("wrong type");
""", """, "ERROR - actual parameter 1 of Class.needsNumber does not match formal "
expected_error="ERROR - actual parameter 1 of Class.needsNumber does " "parameter")
"not match formal parameter")
def testCrDefineFunctionDefinition(self): def testCrDefineFunctionDefinition(self):
self._runCheckerTest(source_code=self._CR_DEFINE_DEFINITION + """ self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() { cr.define('a.b.c', function() {
/** @param {number} num */ /** @param {number} num */
function internalName(num) {} function internalName(num) {}
...@@ -67,11 +77,11 @@ cr.define('a.b.c', function() { ...@@ -67,11 +77,11 @@ cr.define('a.b.c', function() {
}); });
a.b.c.needsNumber("wrong type"); a.b.c.needsNumber("wrong type");
""", expected_error="ERROR - actual parameter 1 of a.b.c.needsNumber does " """, "ERROR - actual parameter 1 of a.b.c.needsNumber does not match formal "
"not match formal parameter") "parameter")
def testCrDefineFunctionAssignment(self): def testCrDefineFunctionAssignment(self):
self._runCheckerTest(source_code=self._CR_DEFINE_DEFINITION + """ self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() { cr.define('a.b.c', function() {
/** @param {number} num */ /** @param {number} num */
var internalName = function(num) {}; var internalName = function(num) {};
...@@ -82,11 +92,11 @@ cr.define('a.b.c', function() { ...@@ -82,11 +92,11 @@ cr.define('a.b.c', function() {
}); });
a.b.c.needsNumber("wrong type"); a.b.c.needsNumber("wrong type");
""", expected_error="ERROR - actual parameter 1 of a.b.c.needsNumber does " """, "ERROR - actual parameter 1 of a.b.c.needsNumber does not match formal "
"not match formal parameter") "parameter")
def testCrDefineConstructorDefinitionPrototypeMethod(self): def testCrDefineConstructorDefinitionPrototypeMethod(self):
self._runCheckerTest(source_code=self._CR_DEFINE_DEFINITION + """ self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() { cr.define('a.b.c', function() {
/** @constructor */ /** @constructor */
function ClassInternalName() {} function ClassInternalName() {}
...@@ -102,11 +112,11 @@ cr.define('a.b.c', function() { ...@@ -102,11 +112,11 @@ cr.define('a.b.c', function() {
}); });
new a.b.c.ClassExternalName().method("wrong type"); new a.b.c.ClassExternalName().method("wrong type");
""", expected_error="ERROR - actual parameter 1 of a.b.c.ClassExternalName." """, "ERROR - actual parameter 1 of a.b.c.ClassExternalName.prototype.method "
"prototype.method does not match formal parameter") "does not match formal parameter")
def testCrDefineConstructorAssignmentPrototypeMethod(self): def testCrDefineConstructorAssignmentPrototypeMethod(self):
self._runCheckerTest(source_code=self._CR_DEFINE_DEFINITION + """ self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() { cr.define('a.b.c', function() {
/** @constructor */ /** @constructor */
var ClassInternalName = function() {}; var ClassInternalName = function() {};
...@@ -122,11 +132,11 @@ cr.define('a.b.c', function() { ...@@ -122,11 +132,11 @@ cr.define('a.b.c', function() {
}); });
new a.b.c.ClassExternalName().method("wrong type"); new a.b.c.ClassExternalName().method("wrong type");
""", expected_error="ERROR - actual parameter 1 of a.b.c.ClassExternalName." """, "ERROR - actual parameter 1 of a.b.c.ClassExternalName.prototype.method "
"prototype.method does not match formal parameter") "does not match formal parameter")
def testCrDefineEnum(self): def testCrDefineEnum(self):
self._runCheckerTest(source_code=self._CR_DEFINE_DEFINITION + """ self._runCheckerTestExpectError(self._CR_DEFINE_DEFINITION + """
cr.define('a.b.c', function() { cr.define('a.b.c', function() {
/** @enum {string} */ /** @enum {string} */
var internalNameForEnum = {key: 'wrong_type'}; var internalNameForEnum = {key: 'wrong_type'};
...@@ -140,8 +150,42 @@ cr.define('a.b.c', function() { ...@@ -140,8 +150,42 @@ cr.define('a.b.c', function() {
function needsNumber(num) {} function needsNumber(num) {}
needsNumber(a.b.c.exportedEnum.key); needsNumber(a.b.c.exportedEnum.key);
""", expected_error="ERROR - actual parameter 1 of needsNumber does not " """, "ERROR - actual parameter 1 of needsNumber does not match formal "
"match formal parameter") "parameter")
def testObjectDefineProperty(self):
self._runCheckerTestExpectSuccess("""
/** @constructor */
function Class() {}
Object.defineProperty(Class.prototype, 'myProperty', {});
alert(new Class().myProperty);
""")
def testCrDefineProperty(self):
self._runCheckerTestExpectSuccess(self._CR_DEFINE_DEFINITION + """
/** @constructor */
function Class() {}
cr.defineProperty(Class.prototype, 'myProperty', cr.PropertyKind.JS);
alert(new Class().myProperty);
""")
def testCrDefinePropertyTypeChecking(self):
self._runCheckerTestExpectError(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 - actual parameter 1 of needsNumber does not match formal "
"parameter")
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -184,4 +184,55 @@ public class ChromePassTest extends CompilerTestCase { ...@@ -184,4 +184,55 @@ public class ChromePassTest extends CompilerTestCase {
null, ChromePass.CR_DEFINE_INVALID_RETURN_IN_FUNCTION); null, ChromePass.CR_DEFINE_INVALID_RETURN_IN_FUNCTION);
} }
public void testObjectDefinePropertyDefinesUnquotedProperty() throws Exception {
test(
"Object.defineProperty(a.b, 'c', {});",
"Object.defineProperty(a.b, 'c', {});\n" +
"/** @type {?} */\n" +
"a.b.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithStringTypeForPropertyKindAttr()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.ATTR);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.ATTR);\n" +
"/** @type {string} */\n" +
"a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithBooleanTypeForPropertyKindBoolAttr()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.BOOL_ATTR);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.BOOL_ATTR);\n" +
"/** @type {boolean} */\n" +
"a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyWithAnyTypeForPropertyKindJs()
throws Exception {
test(
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.JS);",
"cr.defineProperty(a.prototype, 'c', cr.PropertyKind.JS);\n" +
"/** @type {?} */\n" +
"a.prototype.c;");
}
public void testCrDefinePropertyDefinesUnquotedPropertyOnPrototypeWhenFunctionIsPassed()
throws Exception {
test(
"cr.defineProperty(a, 'c', cr.PropertyKind.JS);",
"cr.defineProperty(a, 'c', cr.PropertyKind.JS);\n" +
"/** @type {?} */\n" +
"a.prototype.c;");
}
public void testCrDefinePropertyInvalidPropertyKind()
throws Exception {
test(
"cr.defineProperty(a.b, 'c', cr.PropertyKind.INEXISTENT_KIND);",
null, ChromePass.CR_DEFINE_PROPERTY_INVALID_PROPERTY_KIND);
}
} }
...@@ -75,17 +75,20 @@ var cr = function() { ...@@ -75,17 +75,20 @@ var cr = function() {
/** /**
* Plain old JS property where the backing data is stored as a "private" * Plain old JS property where the backing data is stored as a "private"
* field on the object. * field on the object.
* Use for properties of any type. Type will not be checked.
*/ */
JS: 'js', JS: 'js',
/** /**
* The property backing data is stored as an attribute on an element. * The property backing data is stored as an attribute on an element.
* Use only for properties of type {string}.
*/ */
ATTR: 'attr', ATTR: 'attr',
/** /**
* The property backing data is stored as an attribute on an element. If the * The property backing data is stored as an attribute on an element. If the
* element has the attribute then the value is true. * element has the attribute then the value is true.
* Use only for properties of type {boolean}.
*/ */
BOOL_ATTR: 'boolAttr' BOOL_ATTR: 'boolAttr'
}; };
......
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