Skip to content
This repository has been archived by the owner on Feb 1, 2019. It is now read-only.

Commit

Permalink
Bug 1167595: Support template strings.
Browse files Browse the repository at this point in the history
  • Loading branch information
kmaglione committed May 26, 2015
1 parent 7c88632 commit 8b062f0
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 39 deletions.
2 changes: 1 addition & 1 deletion jetpack/addon-sdk
Submodule addon-sdk updated 729 files
37 changes: 36 additions & 1 deletion tests/test_js_syntax.py
@@ -1,6 +1,6 @@
from nose.tools import eq_

from js_helper import _do_test_raw
from .js_helper import TestCase, _do_test_raw

from validator.testcases.javascript.jstypes import JSWrapper
from validator.testcases.javascript.actions import _get_as_num
Expand Down Expand Up @@ -56,3 +56,38 @@ def test_spidermonkey_warning():
"use strict";
var x = 0999999999999;
""").failed()


class TestTemplateString(TestCase):
WARNING = {"id": ("testcases_chromemanifest", "test_resourcemodules",
"resource_modules")}

def test_template_string(self):
"""Tests that plain template strings trigger warnings like normal
strings."""

self.run_script("`JavaScript-global-property`")
self.assert_failed(with_warnings=[self.WARNING])

def test_template_complex_string(self):
"""Tests that complex template strings trigger warnings like normal
strings."""

self.run_script("`JavaS${'cript-'}glob${'al-pro'}perty`")
self.assert_failed(with_warnings=[self.WARNING])

def test_tagged_template_string(self):
"""Tests that tagged template strings are treated as calls."""

warning = {"id": ("testcases_javascript_instanceactions",
"_call_expression", "called_createelement")}

assert not _do_test_raw("""
d.createElement(); "script";
d.createElement; "script";
""").failed()

self.run_script("""
d.createElement`script`
""")
self.assert_failed(with_warnings=[warning])
71 changes: 44 additions & 27 deletions validator/testcases/javascript/actions.py
@@ -1,3 +1,4 @@
from functools import partial
import re
import types

Expand Down Expand Up @@ -359,6 +360,24 @@ def _define_array(traverser, node):
return arr


def _define_template_strings(traverser, node):
"""Instantiate an array of raw and cooked template strings."""
cooked = JSArray()
cooked.elements = map(traverser._traverse_node, node["cooked"])
raw = JSArray()
raw.elements = map(traverser._traverse_node, node["raw"])

cooked.set("raw", raw, traverser)
return cooked


def _define_template(traverser, node):
"""Instantiate a template literal."""
elements = map(traverser._traverse_node, node["elements"])

return reduce(partial(_binary_op, "+", traverser=traverser), elements)


def _define_literal(traverser, node):
"""
Convert a literal node in the parse tree to its corresponding
Expand Down Expand Up @@ -799,34 +818,33 @@ def _expr_binary(traverser, node):
traverser._debug("BIN_OPERATOR>>%s" % operator)

# Traverse the left half of the binary expression.
traverser._debug("BIN_EXP>>l-value")
traverser.debug_level += 1

if (node["left"]["type"] == "BinaryExpression" and
"__traversal" not in node["left"]):
# Process the left branch of the binary expression directly. This keeps
# the recursion cap in line and speeds up processing of large chains
# of binary expressions.
left = _expr_binary(traverser, node["left"])
node["left"]["__traversal"] = left
else:
left = traverser._traverse_node(node["left"])
with traverser._debug("BIN_EXP>>l-value"):
if (node["left"]["type"] == "BinaryExpression" and
"__traversal" not in node["left"]):
# Process the left branch of the binary expression directly. This
# keeps the recursion cap in line and speeds up processing of
# large chains of binary expressions.
left = _expr_binary(traverser, node["left"])
node["left"]["__traversal"] = left
else:
left = traverser._traverse_node(node["left"])

# Traverse the right half of the binary expression.
traverser._debug("BIN_EXP>>r-value", -1)

if (operator == "instanceof" and
node["right"]["type"] == "Identifier" and
node["right"]["name"] == "Function"):
# We make an exception for instanceof's r-value if it's a dangerous
# global, specifically Function.
traverser.debug_level -= 1
return JSWrapper(True, traverser=traverser)
else:
right = traverser._traverse_node(node["right"])
traverser._debug("Is dirty? %r" % right.dirty, 1)
with traverser._debug("BIN_EXP>>r-value"):
if (operator == "instanceof" and
node["right"]["type"] == "Identifier" and
node["right"]["name"] == "Function"):
# We make an exception for instanceof's r-value if it's a
# dangerous global, specifically Function.
return JSWrapper(True, traverser=traverser)
else:
right = traverser._traverse_node(node["right"])
traverser._debug("Is dirty? %r" % right.dirty, 1)

traverser.debug_level -= 1
return _binary_op(operator, left, right, traverser)

def _binary_op(operator, left, right, traverser):
"""Perform a binary operation on two pre-traversed nodes."""

# Dirty l or r values mean we can skip the expression. A dirty value
# indicates that a lazy operation took place that introduced some
Expand Down Expand Up @@ -880,8 +898,7 @@ def _expr_binary(traverser, node):
left = ""
if right is None:
right = ""
if (isinstance(left, types.StringTypes) or
isinstance(right, types.StringTypes)):
if isinstance(left, basestring) or isinstance(right, basestring):
left = _get_as_str(left)
right = _get_as_str(right)

Expand Down
7 changes: 1 addition & 6 deletions validator/testcases/javascript/jstypes.py
Expand Up @@ -485,17 +485,12 @@ def get_literal_value(self):
def set(self, index, value, traverser=None):
try:
index = int(index)
f_index = float(index)
# Ignore floating point indexes
if index != float(index):
if index != float(index) or index < 0:
return super(JSArray, self).set(value, traverser)
except ValueError:
return super(JSArray, self).set(index, value, traverser)

# JS ignores indexes less than 0
if index < 0:
return

if len(self.elements) > index:
self.elements[index] = JSWrapper(value=value, traverser=traverser)
else:
Expand Down
7 changes: 7 additions & 0 deletions validator/testcases/javascript/nodedefinitions.py
Expand Up @@ -54,6 +54,11 @@ def node(branches=None, dynamic=False, action=None, returns=False,
action=actions._define_array, returns=True),
"ObjectExpression": node(branches=("properties", ),
action=actions._define_obj, returns=True),
"CallSiteObject": node(branches=("elements", ),
action=actions._define_template_strings,
returns=True),
"TemplateLiteral": node(branches=("elements", ),
action=actions._define_template, returns=True),
"FunctionExpression": node(branches=("body", ), dynamic=True,
action=actions._func_expr, returns=True,
is_block=True),
Expand All @@ -73,6 +78,8 @@ def node(branches=None, dynamic=False, action=None, returns=False,
action=actions._new, returns=True),
"CallExpression": node(branches=("callee", "arguments"),
action=actions._call_expression, returns=True),
"TaggedTemplate": node(branches=("callee", "arguments"),
action=actions._call_expression, returns=True),
"MemberExpression": node(branches=("object", "property"),
action=actions.trace_member, returns=True),
"YieldExpression": node(branches=("argument",), returns=True),
Expand Down
20 changes: 16 additions & 4 deletions validator/testcases/javascript/traverser.py
@@ -1,3 +1,4 @@
from collections import defaultdict
import itertools
import re
import types
Expand Down Expand Up @@ -43,10 +44,6 @@ def __init__(self, err, filename, start_line=0, context=None,
# For debugging
self.debug_level = 0

# If we're not debugging, don't waste more cycles than we need to.
if not DEBUG:
self._debug = lambda *args, **kwargs: None

def _debug(self, data, indent=0):
"""Write a message to the console if debugging is enabled."""
if DEBUG:
Expand All @@ -58,6 +55,14 @@ def _debug(self, data, indent=0):
print (". " * (self.debug_level + indent) +
output.encode("ascii", "replace"))

class DebugLevel(object):
def __enter__(self_):
self.debug_level += 1

def __exit__(self_, type, value, traceback):
self.debug_level -= 1
return DebugLevel()

def run(self, data):
if DEBUG:
x = open("/tmp/output.js", "w")
Expand Down Expand Up @@ -130,6 +135,13 @@ def _traverse_node(self, node):
self.position = int(node["loc"]["start"]["column"])

if node.get("type") not in DEFINITIONS:
if node.get("type"):
key = "unknown_node_types"
if key not in self.err.metadata:
self.err.metadata[key] = defaultdict(int)

self.err.metadata[key][node["type"]] += 1

return JSWrapper(JSObject(), traverser=self, dirty=True)

self._debug("TRAVERSE>>%s" % node["type"])
Expand Down

0 comments on commit 8b062f0

Please sign in to comment.