| OLD | NEW | 
|    1 /* |    1 /* | 
|    2  * This file is part of Adblock Plus <https://adblockplus.org/>, |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  * Copyright (C) 2006-present eyeo GmbH |    3  * Copyright (C) 2006-present eyeo GmbH | 
|    4  * |    4  * | 
|    5  * Adblock Plus is free software: you can redistribute it and/or modify |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|    6  * it under the terms of the GNU General Public License version 3 as |    6  * it under the terms of the GNU General Public License version 3 as | 
|    7  * published by the Free Software Foundation. |    7  * published by the Free Software Foundation. | 
|    8  * |    8  * | 
|    9  * Adblock Plus is distributed in the hope that it will be useful, |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
|   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|   12  * GNU General Public License for more details. |   12  * GNU General Public License for more details. | 
|   13  * |   13  * | 
|   14  * You should have received a copy of the GNU General Public License |   14  * You should have received a copy of the GNU General Public License | 
|   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. |   15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|   16  */ |   16  */ | 
|   17  |   17  | 
|   18 /* eslint no-new-func: "off" */ |   18 /* eslint no-new-func: "off" */ | 
|   19  |   19  | 
|   20 "use strict"; |   20 "use strict"; | 
|   21  |   21  | 
 |   22 const assert = require("assert"); | 
|   22 const {createSandbox} = require("./_common"); |   23 const {createSandbox} = require("./_common"); | 
|   23  |   24  | 
|   24 let snippets = null; |   25 let snippets = null; | 
|   25 let parseScript = null; |   26 let parseScript = null; | 
|   26 let compileScript = null; |   27 let compileScript = null; | 
|   27 let Filter = null; |   28 let Filter = null; | 
|   28 let SnippetFilter = null; |   29 let SnippetFilter = null; | 
|   29  |   30  | 
|   30 exports.setUp = function(callback) |   31 describe("Snippets", () => | 
|   31 { |   32 { | 
|   32   let sandboxedRequire = createSandbox(); |   33   beforeEach(() => | 
|   33   ( |   34   { | 
|   34     {Filter, SnippetFilter} = sandboxedRequire("../lib/filterClasses"), |   35     let sandboxedRequire = createSandbox(); | 
|   35     {snippets, parseScript, compileScript} = sandboxedRequire("../lib/snippets") |   36     ( | 
|   36   ); |   37       {Filter, SnippetFilter} = sandboxedRequire("../lib/filterClasses"), | 
|   37  |   38       {snippets, parseScript, compileScript} = sandboxedRequire("../lib/snippets
     ") | 
|   38   callback(); |   39     ); | 
|   39 }; |   40   }); | 
|   40  |   41  | 
|   41 exports.testDomainRestrictions = function(test) |   42   it("Domain Restrictions", () => | 
|   42 { |   43   { | 
|   43   function testScriptMatches(description, filters, domain, expectedMatches) |   44     function testScriptMatches(description, filters, domain, expectedMatches) | 
|   44   { |   45     { | 
|   45     for (let filter of filters.map(Filter.fromText)) |   46       for (let filter of filters.map(Filter.fromText)) | 
|   46     { |   47       { | 
|   47       if (filter instanceof SnippetFilter) |   48         if (filter instanceof SnippetFilter) | 
|   48         snippets.add(filter); |   49           snippets.add(filter); | 
|   49     } |   50       } | 
|   50  |   51  | 
|   51     let matches = snippets.getFiltersForDomain(domain).map( |   52       let matches = snippets.getFiltersForDomain(domain).map( | 
|   52       filter => filter.script |   53         filter => filter.script | 
|   53     ); |   54       ); | 
|   54     test.deepEqual(matches.sort(), expectedMatches.sort(), description); |   55       assert.deepEqual(matches.sort(), expectedMatches.sort(), description); | 
 |   56  | 
 |   57       snippets.clear(); | 
 |   58     } | 
 |   59  | 
 |   60     testScriptMatches( | 
 |   61       "Ignore generic filters", | 
 |   62       [ | 
 |   63         "#$#foo-1", "example.com#$#foo-2", | 
 |   64         "~example.com#$#foo-3" | 
 |   65       ], | 
 |   66       "example.com", | 
 |   67       ["foo-2"] | 
 |   68     ); | 
 |   69     testScriptMatches( | 
 |   70       "Ignore filters that include parent domain but exclude subdomain", | 
 |   71       [ | 
 |   72         "~www.example.com,example.com#$#foo" | 
 |   73       ], | 
 |   74       "www.example.com", | 
 |   75       [] | 
 |   76     ); | 
 |   77     testScriptMatches( | 
 |   78       "Ignore filters for other subdomain", | 
 |   79       [ | 
 |   80         "www.example.com#$#foo-1", | 
 |   81         "other.example.com#$#foo-2" | 
 |   82       ], | 
 |   83       "other.example.com", | 
 |   84       ["foo-2"] | 
 |   85     ); | 
 |   86   }); | 
 |   87  | 
 |   88   it("Filters container", () => | 
 |   89   { | 
 |   90     let events = []; | 
 |   91  | 
 |   92     function eventHandler(...args) | 
 |   93     { | 
 |   94       events.push([...args]); | 
 |   95     } | 
 |   96  | 
 |   97     function compareRules(description, domain, expectedMatches) | 
 |   98     { | 
 |   99       let result = snippets.getFiltersForDomain(domain); | 
 |  100       assert.deepEqual(result.sort(), expectedMatches.sort(), description); | 
 |  101     } | 
 |  102  | 
 |  103     snippets.on("snippets.filterAdded", | 
 |  104                 eventHandler.bind(null, "snippets.filterAdded")); | 
 |  105     snippets.on("snippets.filterRemoved", | 
 |  106                 eventHandler.bind(null, "snippets.filterRemoved")); | 
 |  107     snippets.on("snippets.filtersCleared", | 
 |  108                 eventHandler.bind(null, "snippets.filtersCleared")); | 
 |  109  | 
 |  110     let domainFilter = Filter.fromText("example.com#$#filter1"); | 
 |  111     let subdomainFilter = Filter.fromText("www.example.com#$#filter2"); | 
 |  112     let otherDomainFilter = Filter.fromText("other.example.com#$#filter3"); | 
 |  113  | 
 |  114     snippets.add(domainFilter); | 
 |  115     snippets.add(subdomainFilter); | 
 |  116     snippets.add(otherDomainFilter); | 
 |  117     compareRules( | 
 |  118       "Return all matching filters", | 
 |  119       "www.example.com", | 
 |  120       [domainFilter, subdomainFilter] | 
 |  121     ); | 
 |  122  | 
 |  123     snippets.remove(domainFilter); | 
 |  124     compareRules( | 
 |  125       "Return all matching filters after removing one", | 
 |  126       "www.example.com", | 
 |  127       [subdomainFilter] | 
 |  128     ); | 
|   55  |  129  | 
|   56     snippets.clear(); |  130     snippets.clear(); | 
|   57   } |  131     compareRules( | 
|   58  |  132       "Return no filters after clearing", | 
|   59   testScriptMatches( |  133       "www.example.com", | 
|   60     "Ignore generic filters", |  134       [] | 
|   61     [ |  135     ); | 
|   62       "#$#foo-1", "example.com#$#foo-2", |  136  | 
|   63       "~example.com#$#foo-3" |  137     assert.deepEqual(events, [ | 
 |  138       ["snippets.filterAdded", domainFilter], | 
 |  139       ["snippets.filterAdded", subdomainFilter], | 
 |  140       ["snippets.filterAdded", otherDomainFilter], | 
 |  141       ["snippets.filterRemoved", domainFilter], | 
 |  142       ["snippets.filtersCleared"] | 
|   64     ], |  143     ], | 
|   65     "example.com", |  144                    "Event log"); | 
|   66     ["foo-2"] |  145   }); | 
|   67   ); |  146  | 
|   68   testScriptMatches( |  147   it("Script Parsing", () => | 
|   69     "Ignore filters that include parent domain but exclude subdomain", |  148   { | 
|   70     [ |  149     function checkParsedScript(description, script, expectedTree) | 
|   71       "~www.example.com,example.com#$#foo" |  150     { | 
|   72     ], |  151       let tree = parseScript(script); | 
|   73     "www.example.com", |  152       assert.deepEqual(tree, expectedTree, description); | 
|   74     [] |  153     } | 
|   75   ); |  154  | 
|   76   testScriptMatches( |  155     checkParsedScript("Script with no arguments", "foo", [["foo"]]); | 
|   77     "Ignore filters for other subdomain", |  156     checkParsedScript("Script with one argument", "foo 1", [["foo", "1"]]); | 
|   78     [ |  157     checkParsedScript("Script with two arguments", "foo 1 Hello", | 
|   79       "www.example.com#$#foo-1", |  158                       [["foo", "1", "Hello"]]); | 
|   80       "other.example.com#$#foo-2" |  159     checkParsedScript("Script with argument containing an escaped space", | 
|   81     ], |  160                       "foo Hello\\ world", | 
|   82     "other.example.com", |  161                       [["foo", "Hello world"]]); | 
|   83     ["foo-2"] |  162     checkParsedScript("Script with argument containing a quoted space", | 
|   84   ); |  163                       "foo 'Hello world'", | 
|   85  |  164                       [["foo", "Hello world"]]); | 
|   86   test.done(); |  165     checkParsedScript("Script with argument containing a quoted escaped quote", | 
|   87 }; |  166                       "foo 'Hello \\'world\\''", | 
|   88  |  167                       [["foo", "Hello 'world'"]]); | 
|   89 exports.testSnippetFiltersContainer = function(test) |  168     checkParsedScript("Script with argument containing an escaped semicolon", | 
|   90 { |  169                       "foo TL\\;DR", | 
|   91   let events = []; |  170                       [["foo", "TL;DR"]]); | 
|   92  |  171     checkParsedScript("Script with argument containing a quoted semicolon", | 
|   93   function eventHandler(...args) |  172                       "foo 'TL;DR'", | 
|   94   { |  173                       [["foo", "TL;DR"]]); | 
|   95     events.push([...args]); |  174     checkParsedScript("Script with argument containing single character " + | 
|   96   } |  175                       "escape sequences", | 
|   97  |  176                       "foo yin\\tyang\\n", | 
|   98   function compareRules(description, domain, expectedMatches) |  177                       [["foo", "yin\tyang\n"]]); | 
|   99   { |  178     checkParsedScript("Script with argument containing Unicode escape sequences"
     , | 
|  100     let result = snippets.getFiltersForDomain(domain); |  179                       "foo \\u0062\\ud83d\\ude42r " + | 
|  101     test.deepEqual(result.sort(), expectedMatches.sort(), description); |  180                       "'l\\ud83d\\ude02mbd\\ud83d\\ude02'", [ | 
|  102   } |  181                         ["foo", "b\ud83d\ude42r", "l\ud83d\ude02mbd\ud83d\ude02"
     ] | 
|  103  |  182                       ]); | 
|  104   snippets.on("snippets.filterAdded", |  183     checkParsedScript("Script with multiple commands", "foo; bar", | 
|  105               eventHandler.bind(null, "snippets.filterAdded")); |  184                       [["foo"], ["bar"]]); | 
|  106   snippets.on("snippets.filterRemoved", |  185     checkParsedScript("Script with multiple commands and multiple arguments each
     ", | 
|  107               eventHandler.bind(null, "snippets.filterRemoved")); |  186                       "foo 1 Hello; bar world! #", | 
|  108   snippets.on("snippets.filtersCleared", |  187                       [["foo", "1", "Hello"], ["bar", "world!", "#"]]); | 
|  109               eventHandler.bind(null, "snippets.filtersCleared")); |  188     checkParsedScript("Script with multiple commands and multiple " + | 
|  110  |  189                       "escaped and quoted arguments each", | 
|  111   let domainFilter = Filter.fromText("example.com#$#filter1"); |  190                       "foo 1 'Hello, \\'Tommy\\'!' ;" + | 
|  112   let subdomainFilter = Filter.fromText("www.example.com#$#filter2"); |  191                       "bar Hi!\\ How\\ are\\ you? http://example.com", [ | 
|  113   let otherDomainFilter = Filter.fromText("other.example.com#$#filter3"); |  192                         ["foo", "1", "Hello, 'Tommy'!"], | 
|  114  |  193                         ["bar", "Hi! How are you?", "http://example.com"] | 
|  115   snippets.add(domainFilter); |  194                       ]); | 
|  116   snippets.add(subdomainFilter); |  195     checkParsedScript("Script with command names containing " + | 
|  117   snippets.add(otherDomainFilter); |  196                       "whitespace (spaces, tabs, newlines, etc.), " + | 
|  118   compareRules( |  197                       "quotes, and semicolons", | 
|  119     "Return all matching filters", |  198                       "fo\\'\\ \\ \\\t\\\n\\;o 1 2 3; 'b a  r' 1 2", | 
|  120     "www.example.com", |  199                       [["fo'  \t\n;o", "1", "2", "3"], ["b a  r", "1", "2"]]); | 
|  121     [domainFilter, subdomainFilter] |  200     checkParsedScript("Script containing Unicode composite characters", | 
|  122   ); |  201                       "f\ud83d\ude42\ud83d\ude42 b\ud83d\ude02r", | 
|  123  |  202                       [["f\ud83d\ude42\ud83d\ude42", "b\ud83d\ude02r"]]); | 
|  124   snippets.remove(domainFilter); |  203     checkParsedScript("Script with no-op commands", "foo; ;;; ;  ; bar 1", | 
|  125   compareRules( |  204                       [["foo"], ["bar", "1"]]); | 
|  126     "Return all matching filters after removing one", |  205     checkParsedScript("Script with blank argument in the middle", "foo '' Hello"
     , | 
|  127     "www.example.com", |  206                       [["foo", "", "Hello"]]); | 
|  128     [subdomainFilter] |  207     checkParsedScript("Script with blank argument at the end", "foo Hello ''", | 
|  129   ); |  208                       [["foo", "Hello", ""]]); | 
|  130  |  209     checkParsedScript("Script with consecutive blank arguments", "foo '' ''", | 
|  131   snippets.clear(); |  210                       [["foo", "", ""]]); | 
|  132   compareRules( |  211  | 
|  133     "Return no filters after clearing", |  212     // Undocumented quirks (#6853). | 
|  134     "www.example.com", |  213     checkParsedScript("Script with quotes within an argument", "foo Hello''world
     ", | 
|  135     [] |  214                       [["foo", "Helloworld"]]); | 
|  136   ); |  215     checkParsedScript("Script with quotes within an argument containing whitespa
     ce", | 
|  137  |  216                       "foo Hello' 'world", | 
|  138   test.deepEqual(events, [ |  217                       [["foo", "Hello world"]]); | 
|  139     ["snippets.filterAdded", domainFilter], |  218     checkParsedScript("Script with quotes within an argument containing non-whit
     espace", | 
|  140     ["snippets.filterAdded", subdomainFilter], |  219                       "foo Hello','world", | 
|  141     ["snippets.filterAdded", otherDomainFilter], |  220                       [["foo", "Hello,world"]]); | 
|  142     ["snippets.filterRemoved", domainFilter], |  221     checkParsedScript("Script with quotes within an argument containing whitespa
     ce and non-whitespace", | 
|  143     ["snippets.filtersCleared"] |  222                       "foo Hello', 'world", | 
|  144   ], |  223                       [["foo", "Hello, world"]]); | 
|  145   "Event log"); |  224     checkParsedScript("Script with opening quote at the beginning of an argument
     ", | 
|  146  |  225                       "foo 'Hello, 'world", | 
|  147   test.done(); |  226                       [["foo", "Hello, world"]]); | 
|  148 }; |  227     checkParsedScript("Script with closing quote at the end of an argument", | 
|  149  |  228                       "foo Hello,' world'", | 
|  150 exports.testScriptParsing = function(test) |  229                       [["foo", "Hello, world"]]); | 
|  151 { |  230  | 
|  152   function checkParsedScript(description, script, expectedTree) |  231     checkParsedScript("Script with closing quote missing", "foo 'Hello, world", | 
|  153   { |  232                       []); | 
|  154     let tree = parseScript(script); |  233     checkParsedScript("Script with closing quote missing in last command", | 
|  155     test.deepEqual(tree, expectedTree, description); |  234                       "foo Hello; bar 'How are you?", | 
|  156   } |  235                       [["foo", "Hello"]]); | 
|  157  |  236     checkParsedScript("Script ending with a backslash", | 
|  158   checkParsedScript("Script with no arguments", "foo", [["foo"]]); |  237                       "foo Hello; bar 'How are you?' \\", | 
|  159   checkParsedScript("Script with one argument", "foo 1", [["foo", "1"]]); |  238                       [["foo", "Hello"]]); | 
|  160   checkParsedScript("Script with two arguments", "foo 1 Hello", |  239   }); | 
|  161                     [["foo", "1", "Hello"]]); |  240  | 
|  162   checkParsedScript("Script with argument containing an escaped space", |  241   it("Script Compilation", () => | 
|  163                     "foo Hello\\ world", |  242   { | 
|  164                     [["foo", "Hello world"]]); |  243     let libraries = [ | 
|  165   checkParsedScript("Script with argument containing a quoted space", |  244       ` | 
|  166                     "foo 'Hello world'", |  | 
|  167                     [["foo", "Hello world"]]); |  | 
|  168   checkParsedScript("Script with argument containing a quoted escaped quote", |  | 
|  169                     "foo 'Hello \\'world\\''", |  | 
|  170                     [["foo", "Hello 'world'"]]); |  | 
|  171   checkParsedScript("Script with argument containing an escaped semicolon", |  | 
|  172                     "foo TL\\;DR", |  | 
|  173                     [["foo", "TL;DR"]]); |  | 
|  174   checkParsedScript("Script with argument containing a quoted semicolon", |  | 
|  175                     "foo 'TL;DR'", |  | 
|  176                     [["foo", "TL;DR"]]); |  | 
|  177   checkParsedScript("Script with argument containing single character " + |  | 
|  178                     "escape sequences", |  | 
|  179                     "foo yin\\tyang\\n", |  | 
|  180                     [["foo", "yin\tyang\n"]]); |  | 
|  181   checkParsedScript("Script with argument containing Unicode escape sequences", |  | 
|  182                     "foo \\u0062\\ud83d\\ude42r " + |  | 
|  183                     "'l\\ud83d\\ude02mbd\\ud83d\\ude02'", [ |  | 
|  184                       ["foo", "b\ud83d\ude42r", "l\ud83d\ude02mbd\ud83d\ude02"] |  | 
|  185                     ]); |  | 
|  186   checkParsedScript("Script with multiple commands", "foo; bar", |  | 
|  187                     [["foo"], ["bar"]]); |  | 
|  188   checkParsedScript("Script with multiple commands and multiple arguments each", |  | 
|  189                     "foo 1 Hello; bar world! #", |  | 
|  190                     [["foo", "1", "Hello"], ["bar", "world!", "#"]]); |  | 
|  191   checkParsedScript("Script with multiple commands and multiple " + |  | 
|  192                     "escaped and quoted arguments each", |  | 
|  193                     "foo 1 'Hello, \\'Tommy\\'!' ;" + |  | 
|  194                     "bar Hi!\\ How\\ are\\ you? http://example.com", [ |  | 
|  195                       ["foo", "1", "Hello, 'Tommy'!"], |  | 
|  196                       ["bar", "Hi! How are you?", "http://example.com"] |  | 
|  197                     ]); |  | 
|  198   checkParsedScript("Script with command names containing " + |  | 
|  199                     "whitespace (spaces, tabs, newlines, etc.), " + |  | 
|  200                     "quotes, and semicolons", |  | 
|  201                     "fo\\'\\ \\ \\\t\\\n\\;o 1 2 3; 'b a  r' 1 2", |  | 
|  202                     [["fo'  \t\n;o", "1", "2", "3"], ["b a  r", "1", "2"]]); |  | 
|  203   checkParsedScript("Script containing Unicode composite characters", |  | 
|  204                     "f\ud83d\ude42\ud83d\ude42 b\ud83d\ude02r", |  | 
|  205                     [["f\ud83d\ude42\ud83d\ude42", "b\ud83d\ude02r"]]); |  | 
|  206   checkParsedScript("Script with no-op commands", "foo; ;;; ;  ; bar 1", |  | 
|  207                     [["foo"], ["bar", "1"]]); |  | 
|  208   checkParsedScript("Script with blank argument in the middle", "foo '' Hello", |  | 
|  209                     [["foo", "", "Hello"]]); |  | 
|  210   checkParsedScript("Script with blank argument at the end", "foo Hello ''", |  | 
|  211                     [["foo", "Hello", ""]]); |  | 
|  212   checkParsedScript("Script with consecutive blank arguments", "foo '' ''", |  | 
|  213                     [["foo", "", ""]]); |  | 
|  214  |  | 
|  215   // Undocumented quirks (#6853). |  | 
|  216   checkParsedScript("Script with quotes within an argument", "foo Hello''world", |  | 
|  217                     [["foo", "Helloworld"]]); |  | 
|  218   checkParsedScript("Script with quotes within an argument containing whitespace
     ", |  | 
|  219                     "foo Hello' 'world", |  | 
|  220                     [["foo", "Hello world"]]); |  | 
|  221   checkParsedScript("Script with quotes within an argument containing non-whites
     pace", |  | 
|  222                     "foo Hello','world", |  | 
|  223                     [["foo", "Hello,world"]]); |  | 
|  224   checkParsedScript("Script with quotes within an argument containing whitespace
      and non-whitespace", |  | 
|  225                     "foo Hello', 'world", |  | 
|  226                     [["foo", "Hello, world"]]); |  | 
|  227   checkParsedScript("Script with opening quote at the beginning of an argument", |  | 
|  228                     "foo 'Hello, 'world", |  | 
|  229                     [["foo", "Hello, world"]]); |  | 
|  230   checkParsedScript("Script with closing quote at the end of an argument", |  | 
|  231                     "foo Hello,' world'", |  | 
|  232                     [["foo", "Hello, world"]]); |  | 
|  233  |  | 
|  234   checkParsedScript("Script with closing quote missing", "foo 'Hello, world", |  | 
|  235                     []); |  | 
|  236   checkParsedScript("Script with closing quote missing in last command", |  | 
|  237                     "foo Hello; bar 'How are you?", |  | 
|  238                     [["foo", "Hello"]]); |  | 
|  239   checkParsedScript("Script ending with a backslash", |  | 
|  240                     "foo Hello; bar 'How are you?' \\", |  | 
|  241                     [["foo", "Hello"]]); |  | 
|  242  |  | 
|  243   test.done(); |  | 
|  244 }; |  | 
|  245  |  | 
|  246 exports.testScriptCompilation = function(test) |  | 
|  247 { |  | 
|  248   let libraries = [ |  | 
|  249     ` |  | 
|  250       let foo = 0; |  245       let foo = 0; | 
|  251  |  246  | 
|  252       exports.setFoo = function(value) |  247       exports.setFoo = function(value) | 
|  253       { |  248       { | 
|  254         foo = value; |  249         foo = value; | 
|  255       }; |  250       }; | 
|  256  |  251  | 
|  257       exports.assertFoo = function(expected) |  252       exports.assertFoo = function(expected) | 
|  258       { |  253       { | 
|  259         if (foo != expected) |  254         if (foo != expected) | 
|  260           throw new Error("Value mismatch"); |  255           throw new Error("Value mismatch"); | 
|  261       }; |  256       }; | 
|  262     ` |  257     ` | 
|  263   ]; |  258     ]; | 
|  264  |  259  | 
|  265   let template = ` |  260     let template = ` | 
|  266     "use strict"; |  261     "use strict"; | 
|  267     { |  262     { | 
|  268       const libraries = ${JSON.stringify(libraries)}; |  263       const libraries = ${JSON.stringify(libraries)}; | 
|  269  |  264  | 
|  270       const script = {{{script}}}; |  265       const script = {{{script}}}; | 
|  271  |  266  | 
|  272       let imports = Object.create(null); |  267       let imports = Object.create(null); | 
|  273       for (let library of libraries) |  268       for (let library of libraries) | 
|  274         new Function("exports", library)(imports); |  269         new Function("exports", library)(imports); | 
|  275  |  270  | 
|  276       for (let [name, ...args] of script) |  271       for (let [name, ...args] of script) | 
|  277       { |  272       { | 
|  278         if (Object.prototype.hasOwnProperty.call(imports, name)) |  273         if (Object.prototype.hasOwnProperty.call(imports, name)) | 
|  279         { |  274         { | 
|  280           let value = imports[name]; |  275           let value = imports[name]; | 
|  281           if (typeof value == "function") |  276           if (typeof value == "function") | 
|  282             value(...args); |  277             value(...args); | 
|  283         } |  278         } | 
|  284       } |  279       } | 
|  285     } |  280     } | 
|  286   `; |  281   `; | 
|  287  |  282  | 
|  288   function verifyExecutable(script) |  283     function verifyExecutable(script) | 
|  289   { |  284     { | 
|  290     let actual = compileScript(script, libraries); |  285       let actual = compileScript(script, libraries); | 
|  291     let expected = template.replace("{{{script}}}", |  286       let expected = template.replace("{{{script}}}", | 
|  292                                     JSON.stringify(parseScript(script))); |  287                                       JSON.stringify(parseScript(script))); | 
|  293  |  288  | 
|  294     test.equal(expected, actual); |  289       assert.equal(expected, actual); | 
|  295   } |  290     } | 
|  296  |  291  | 
|  297   verifyExecutable("hello 'How are you?'"); |  292     verifyExecutable("hello 'How are you?'"); | 
|  298  |  293  | 
|  299   // Test script execution. |  294     // Test script execution. | 
|  300   new Function(compileScript("setFoo 123; assertFoo 123", libraries))(); |  295     new Function(compileScript("setFoo 123; assertFoo 123", libraries))(); | 
|  301  |  296  | 
|  302   // Override setFoo in a second library, without overriding assertFoo. A |  297     // Override setFoo in a second library, without overriding assertFoo. A | 
|  303   // couple of things to note here: (1) each library has its own variables; |  298     // couple of things to note here: (1) each library has its own variables; | 
|  304   // (2) script execution is stateless, i.e. the values are not retained |  299     // (2) script execution is stateless, i.e. the values are not retained | 
|  305   // between executions. In the example below, assertFoo does not find 456 but |  300     // between executions. In the example below, assertFoo does not find 456 but | 
|  306   // it doesn't find 123 either. It's the initial value 0. |  301     // it doesn't find 123 either. It's the initial value 0. | 
|  307   new Function( |  302     new Function( | 
|  308     compileScript("setFoo 456; assertFoo 0", [ |  303       compileScript("setFoo 456; assertFoo 0", [ | 
|  309       ...libraries, "let foo = 1; exports.setFoo = value => { foo = value; };" |  304         ...libraries, "let foo = 1; exports.setFoo = value => { foo = value; };" | 
|  310     ]) |  305       ]) | 
|  311   )(); |  306     )(); | 
|  312  |  307   }); | 
|  313   test.done(); |  308 }); | 
|  314 }; |  | 
| OLD | NEW |