Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/matcher.js

Issue 29375915: Issue 4878 - Start using ESLint for adblockpluscore (Closed)
Patch Set: Fix Cu.imports in synchronizer.js that I missed previously Created March 14, 2017, 9:03 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 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 "use strict";
19
18 /** 20 /**
19 * @fileOverview Matcher class implementing matching addresses against a list of filters. 21 * @fileOverview Matcher class implementing matching addresses against
22 * a list of filters.
20 */ 23 */
21 24
22 let {Filter, RegExpFilter, WhitelistFilter} = require("filterClasses"); 25 const {Filter, WhitelistFilter} = require("filterClasses");
23 26
24 /** 27 /**
25 * Blacklist/whitelist filter matching 28 * Blacklist/whitelist filter matching
26 * @constructor 29 * @constructor
27 */ 30 */
28 function Matcher() 31 function Matcher()
29 { 32 {
30 this.clear(); 33 this.clear();
31 } 34 }
32 exports.Matcher = Matcher; 35 exports.Matcher = Matcher;
33 36
34 Matcher.prototype = { 37 Matcher.prototype = {
35 /** 38 /**
36 * Lookup table for filters by their associated keyword 39 * Lookup table for filters by their associated keyword
37 * @type Object 40 * @type {Object}
38 */ 41 */
39 filterByKeyword: null, 42 filterByKeyword: null,
40 43
41 /** 44 /**
42 * Lookup table for keywords by the filter text 45 * Lookup table for keywords by the filter text
43 * @type Object 46 * @type {Object}
44 */ 47 */
45 keywordByFilter: null, 48 keywordByFilter: null,
46 49
47 /** 50 /**
48 * Removes all known filters 51 * Removes all known filters
49 */ 52 */
50 clear: function() 53 clear()
51 { 54 {
52 this.filterByKeyword = Object.create(null); 55 this.filterByKeyword = Object.create(null);
53 this.keywordByFilter = Object.create(null); 56 this.keywordByFilter = Object.create(null);
54 }, 57 },
55 58
56 /** 59 /**
57 * Adds a filter to the matcher 60 * Adds a filter to the matcher
58 * @param {RegExpFilter} filter 61 * @param {RegExpFilter} filter
59 */ 62 */
60 add: function(filter) 63 add(filter)
61 { 64 {
62 if (filter.text in this.keywordByFilter) 65 if (filter.text in this.keywordByFilter)
63 return; 66 return;
64 67
65 // Look for a suitable keyword 68 // Look for a suitable keyword
66 let keyword = this.findKeyword(filter); 69 let keyword = this.findKeyword(filter);
67 let oldEntry = this.filterByKeyword[keyword]; 70 let oldEntry = this.filterByKeyword[keyword];
68 if (typeof oldEntry == "undefined") 71 if (typeof oldEntry == "undefined")
69 this.filterByKeyword[keyword] = filter; 72 this.filterByKeyword[keyword] = filter;
70 else if (oldEntry.length == 1) 73 else if (oldEntry.length == 1)
71 this.filterByKeyword[keyword] = [oldEntry, filter]; 74 this.filterByKeyword[keyword] = [oldEntry, filter];
72 else 75 else
73 oldEntry.push(filter); 76 oldEntry.push(filter);
74 this.keywordByFilter[filter.text] = keyword; 77 this.keywordByFilter[filter.text] = keyword;
75 }, 78 },
76 79
77 /** 80 /**
78 * Removes a filter from the matcher 81 * Removes a filter from the matcher
79 * @param {RegExpFilter} filter 82 * @param {RegExpFilter} filter
80 */ 83 */
81 remove: function(filter) 84 remove(filter)
82 { 85 {
83 if (!(filter.text in this.keywordByFilter)) 86 if (!(filter.text in this.keywordByFilter))
84 return; 87 return;
85 88
86 let keyword = this.keywordByFilter[filter.text]; 89 let keyword = this.keywordByFilter[filter.text];
87 let list = this.filterByKeyword[keyword]; 90 let list = this.filterByKeyword[keyword];
88 if (list.length <= 1) 91 if (list.length <= 1)
89 delete this.filterByKeyword[keyword]; 92 delete this.filterByKeyword[keyword];
90 else 93 else
91 { 94 {
92 let index = list.indexOf(filter); 95 let index = list.indexOf(filter);
93 if (index >= 0) 96 if (index >= 0)
94 { 97 {
95 list.splice(index, 1); 98 list.splice(index, 1);
96 if (list.length == 1) 99 if (list.length == 1)
97 this.filterByKeyword[keyword] = list[0]; 100 this.filterByKeyword[keyword] = list[0];
98 } 101 }
99 } 102 }
100 103
101 delete this.keywordByFilter[filter.text]; 104 delete this.keywordByFilter[filter.text];
102 }, 105 },
103 106
104 /** 107 /**
105 * Chooses a keyword to be associated with the filter 108 * Chooses a keyword to be associated with the filter
106 * @param {String} text text representation of the filter 109 * @param {Filter} filter
107 * @return {String} keyword (might be empty string) 110 * @return {string} keyword or an empty string if no keyword could be found
108 */ 111 */
109 findKeyword: function(filter) 112 findKeyword(filter)
110 { 113 {
111 let result = ""; 114 let result = "";
112 let text = filter.text; 115 let {text} = filter;
113 if (Filter.regexpRegExp.test(text)) 116 if (Filter.regexpRegExp.test(text))
114 return result; 117 return result;
115 118
116 // Remove options 119 // Remove options
117 let match = Filter.optionsRegExp.exec(text); 120 let match = Filter.optionsRegExp.exec(text);
118 if (match) 121 if (match)
119 text = match.input.substr(0, match.index); 122 text = match.input.substr(0, match.index);
120 123
121 // Remove whitelist marker 124 // Remove whitelist marker
122 if (text.substr(0, 2) == "@@") 125 if (text.substr(0, 2) == "@@")
123 text = text.substr(2); 126 text = text.substr(2);
124 127
125 let candidates = text.toLowerCase().match(/[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0 -9%*])/g); 128 let candidates = text.toLowerCase().match(
129 /[^a-z0-9%*][a-z0-9%]{3,}(?=[^a-z0-9%*])/g
130 );
126 if (!candidates) 131 if (!candidates)
127 return result; 132 return result;
128 133
129 let hash = this.filterByKeyword; 134 let hash = this.filterByKeyword;
130 let resultCount = 0xFFFFFF; 135 let resultCount = 0xFFFFFF;
131 let resultLength = 0; 136 let resultLength = 0;
132 for (let i = 0, l = candidates.length; i < l; i++) 137 for (let i = 0, l = candidates.length; i < l; i++)
133 { 138 {
134 let candidate = candidates[i].substr(1); 139 let candidate = candidates[i].substr(1);
135 let count = (candidate in hash ? hash[candidate].length : 0); 140 let count = (candidate in hash ? hash[candidate].length : 0);
136 if (count < resultCount || (count == resultCount && candidate.length > res ultLength)) 141 if (count < resultCount ||
142 (count == resultCount && candidate.length > resultLength))
137 { 143 {
138 result = candidate; 144 result = candidate;
139 resultCount = count; 145 resultCount = count;
140 resultLength = candidate.length; 146 resultLength = candidate.length;
141 } 147 }
142 } 148 }
143 return result; 149 return result;
144 }, 150 },
145 151
146 /** 152 /**
147 * Checks whether a particular filter is being matched against. 153 * Checks whether a particular filter is being matched against.
154 * @param {RegExpFilter} filter
155 * @return {boolean}
148 */ 156 */
149 hasFilter: function(/**RegExpFilter*/ filter) /**Boolean*/ 157 hasFilter(filter)
150 { 158 {
151 return (filter.text in this.keywordByFilter); 159 return (filter.text in this.keywordByFilter);
152 }, 160 },
153 161
154 /** 162 /**
155 * Returns the keyword used for a filter, null for unknown filters. 163 * Returns the keyword used for a filter, null for unknown filters.
164 * @param {RegExpFilter} filter
165 * @return {string}
156 */ 166 */
157 getKeywordForFilter: function(/**RegExpFilter*/ filter) /**String*/ 167 getKeywordForFilter(filter)
158 { 168 {
159 if (filter.text in this.keywordByFilter) 169 if (filter.text in this.keywordByFilter)
160 return this.keywordByFilter[filter.text]; 170 return this.keywordByFilter[filter.text];
161 else 171 return null;
162 return null;
163 }, 172 },
164 173
165 /** 174 /**
166 * Checks whether the entries for a particular keyword match a URL 175 * Checks whether the entries for a particular keyword match a URL
176 * @param {string} keyword
177 * @param {string} location
178 * @param {number} typeMask
179 * @param {string} docDomain
180 * @param {boolean} thirdParty
181 * @param {string} sitekey
182 * @param {boolean} specificOnly
183 * @return {?Filter}
167 */ 184 */
168 _checkEntryMatch: function(keyword, location, typeMask, docDomain, thirdParty, sitekey, specificOnly) 185 _checkEntryMatch(keyword, location, typeMask, docDomain, thirdParty, sitekey,
186 specificOnly)
169 { 187 {
170 let list = this.filterByKeyword[keyword]; 188 let list = this.filterByKeyword[keyword];
171 for (let i = 0; i < list.length; i++) 189 for (let i = 0; i < list.length; i++)
172 { 190 {
173 let filter = list[i]; 191 let filter = list[i];
174 192
175 if (specificOnly && filter.isGeneric() && 193 if (specificOnly && filter.isGeneric() &&
176 !(filter instanceof WhitelistFilter)) 194 !(filter instanceof WhitelistFilter))
177 continue; 195 continue;
178 196
179 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey)) 197 if (filter.matches(location, typeMask, docDomain, thirdParty, sitekey))
180 return filter; 198 return filter;
181 } 199 }
182 return null; 200 return null;
183 }, 201 },
184 202
185 /** 203 /**
186 * Tests whether the URL matches any of the known filters 204 * Tests whether the URL matches any of the known filters
187 * @param {String} location URL to be tested 205 * @param {string} location
188 * @param {number} typeMask bitmask of content / request types to match 206 * URL to be tested
189 * @param {String} docDomain domain name of the document that loads the URL 207 * @param {number} typeMask
190 * @param {Boolean} thirdParty should be true if the URL is a third-party requ est 208 * bitmask of content / request types to match
191 * @param {String} sitekey public key provided by the document 209 * @param {string} docDomain
192 * @param {Boolean} specificOnly should be true if generic matches should be i gnored 210 * domain name of the document that loads the URL
193 * @return {RegExpFilter} matching filter or null 211 * @param {boolean} thirdParty
212 * should be true if the URL is a third-party request
213 * @param {string} sitekey
214 * public key provided by the document
215 * @param {boolean} specificOnly
216 * should be true if generic matches should be ignored
217 * @return {?RegExpFilter}
218 * matching filter or null
194 */ 219 */
195 matchesAny: function(location, typeMask, docDomain, thirdParty, sitekey, speci ficOnly) 220 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
196 { 221 {
197 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); 222 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
198 if (candidates === null) 223 if (candidates === null)
199 candidates = []; 224 candidates = [];
200 candidates.push(""); 225 candidates.push("");
201 for (let i = 0, l = candidates.length; i < l; i++) 226 for (let i = 0, l = candidates.length; i < l; i++)
202 { 227 {
203 let substr = candidates[i]; 228 let substr = candidates[i];
204 if (substr in this.filterByKeyword) 229 if (substr in this.filterByKeyword)
205 { 230 {
206 let result = this._checkEntryMatch(substr, location, typeMask, docDomain , thirdParty, sitekey, specificOnly); 231 let result = this._checkEntryMatch(substr, location, typeMask,
232 docDomain, thirdParty, sitekey,
233 specificOnly);
207 if (result) 234 if (result)
208 return result; 235 return result;
209 } 236 }
210 } 237 }
211 238
212 return null; 239 return null;
213 } 240 }
214 }; 241 };
215 242
216 /** 243 /**
217 * Combines a matcher for blocking and exception rules, automatically sorts 244 * Combines a matcher for blocking and exception rules, automatically sorts
218 * rules into two Matcher instances. 245 * rules into two Matcher instances.
219 * @constructor 246 * @constructor
247 * @augments Matcher
220 */ 248 */
221 function CombinedMatcher() 249 function CombinedMatcher()
222 { 250 {
223 this.blacklist = new Matcher(); 251 this.blacklist = new Matcher();
224 this.whitelist = new Matcher(); 252 this.whitelist = new Matcher();
225 this.resultCache = Object.create(null); 253 this.resultCache = Object.create(null);
226 } 254 }
227 exports.CombinedMatcher = CombinedMatcher; 255 exports.CombinedMatcher = CombinedMatcher;
228 256
229 /** 257 /**
230 * Maximal number of matching cache entries to be kept 258 * Maximal number of matching cache entries to be kept
231 * @type Number 259 * @type {number}
232 */ 260 */
233 CombinedMatcher.maxCacheEntries = 1000; 261 CombinedMatcher.maxCacheEntries = 1000;
234 262
235 CombinedMatcher.prototype = 263 CombinedMatcher.prototype =
236 { 264 {
237 /** 265 /**
238 * Matcher for blocking rules. 266 * Matcher for blocking rules.
239 * @type Matcher 267 * @type {Matcher}
240 */ 268 */
241 blacklist: null, 269 blacklist: null,
242 270
243 /** 271 /**
244 * Matcher for exception rules. 272 * Matcher for exception rules.
245 * @type Matcher 273 * @type {Matcher}
246 */ 274 */
247 whitelist: null, 275 whitelist: null,
248 276
249 /** 277 /**
250 * Lookup table of previous matchesAny results 278 * Lookup table of previous matchesAny results
251 * @type Object 279 * @type {Object}
252 */ 280 */
253 resultCache: null, 281 resultCache: null,
254 282
255 /** 283 /**
256 * Number of entries in resultCache 284 * Number of entries in resultCache
257 * @type Number 285 * @type {number}
258 */ 286 */
259 cacheEntries: 0, 287 cacheEntries: 0,
260 288
261 /** 289 /**
262 * @see Matcher#clear 290 * @see Matcher#clear
263 */ 291 */
264 clear: function() 292 clear()
265 { 293 {
266 this.blacklist.clear(); 294 this.blacklist.clear();
267 this.whitelist.clear(); 295 this.whitelist.clear();
268 this.resultCache = Object.create(null); 296 this.resultCache = Object.create(null);
269 this.cacheEntries = 0; 297 this.cacheEntries = 0;
270 }, 298 },
271 299
272 /** 300 /**
273 * @see Matcher#add 301 * @see Matcher#add
302 * @param {Filter} filter
274 */ 303 */
275 add: function(filter) 304 add(filter)
276 { 305 {
277 if (filter instanceof WhitelistFilter) 306 if (filter instanceof WhitelistFilter)
278 this.whitelist.add(filter); 307 this.whitelist.add(filter);
279 else 308 else
280 this.blacklist.add(filter); 309 this.blacklist.add(filter);
281 310
282 if (this.cacheEntries > 0) 311 if (this.cacheEntries > 0)
283 { 312 {
284 this.resultCache = Object.create(null); 313 this.resultCache = Object.create(null);
285 this.cacheEntries = 0; 314 this.cacheEntries = 0;
286 } 315 }
287 }, 316 },
288 317
289 /** 318 /**
290 * @see Matcher#remove 319 * @see Matcher#remove
320 * @param {Filter} filter
291 */ 321 */
292 remove: function(filter) 322 remove(filter)
293 { 323 {
294 if (filter instanceof WhitelistFilter) 324 if (filter instanceof WhitelistFilter)
295 this.whitelist.remove(filter); 325 this.whitelist.remove(filter);
296 else 326 else
297 this.blacklist.remove(filter); 327 this.blacklist.remove(filter);
298 328
299 if (this.cacheEntries > 0) 329 if (this.cacheEntries > 0)
300 { 330 {
301 this.resultCache = Object.create(null); 331 this.resultCache = Object.create(null);
302 this.cacheEntries = 0; 332 this.cacheEntries = 0;
303 } 333 }
304 }, 334 },
305 335
306 /** 336 /**
307 * @see Matcher#findKeyword 337 * @see Matcher#findKeyword
338 * @param {Filter} filter
339 * @return {string} keyword
308 */ 340 */
309 findKeyword: function(filter) 341 findKeyword(filter)
310 { 342 {
311 if (filter instanceof WhitelistFilter) 343 if (filter instanceof WhitelistFilter)
312 return this.whitelist.findKeyword(filter); 344 return this.whitelist.findKeyword(filter);
313 else 345 return this.blacklist.findKeyword(filter);
314 return this.blacklist.findKeyword(filter);
315 }, 346 },
316 347
317 /** 348 /**
318 * @see Matcher#hasFilter 349 * @see Matcher#hasFilter
350 * @param {Filter} filter
351 * @return {boolean}
319 */ 352 */
320 hasFilter: function(filter) 353 hasFilter(filter)
321 { 354 {
322 if (filter instanceof WhitelistFilter) 355 if (filter instanceof WhitelistFilter)
323 return this.whitelist.hasFilter(filter); 356 return this.whitelist.hasFilter(filter);
324 else 357 return this.blacklist.hasFilter(filter);
325 return this.blacklist.hasFilter(filter);
326 }, 358 },
327 359
328 /** 360 /**
329 * @see Matcher#getKeywordForFilter 361 * @see Matcher#getKeywordForFilter
362 * @param {Filter} filter
363 * @return {string} keyword
330 */ 364 */
331 getKeywordForFilter: function(filter) 365 getKeywordForFilter(filter)
332 { 366 {
333 if (filter instanceof WhitelistFilter) 367 if (filter instanceof WhitelistFilter)
334 return this.whitelist.getKeywordForFilter(filter); 368 return this.whitelist.getKeywordForFilter(filter);
335 else 369 return this.blacklist.getKeywordForFilter(filter);
336 return this.blacklist.getKeywordForFilter(filter);
337 }, 370 },
338 371
339 /** 372 /**
340 * Checks whether a particular filter is slow 373 * Checks whether a particular filter is slow
374 * @param {RegExpFilter} filter
375 * @return {boolean}
341 */ 376 */
342 isSlowFilter: function(/**RegExpFilter*/ filter) /**Boolean*/ 377 isSlowFilter(filter)
343 { 378 {
344 let matcher = (filter instanceof WhitelistFilter ? this.whitelist : this.bla cklist); 379 let matcher = (
380 filter instanceof WhitelistFilter ? this.whitelist : this.blacklist
381 );
345 if (matcher.hasFilter(filter)) 382 if (matcher.hasFilter(filter))
346 return !matcher.getKeywordForFilter(filter); 383 return !matcher.getKeywordForFilter(filter);
347 else 384 return !matcher.findKeyword(filter);
348 return !matcher.findKeyword(filter);
349 }, 385 },
350 386
351 /** 387 /**
352 * Optimized filter matching testing both whitelist and blacklist matchers 388 * Optimized filter matching testing both whitelist and blacklist matchers
353 * simultaneously. For parameters see Matcher.matchesAny(). 389 * simultaneously. For parameters see Matcher.matchesAny().
354 * @see Matcher#matchesAny 390 * @see Matcher#matchesAny
391 * @inheritdoc
355 */ 392 */
356 matchesAnyInternal: function(location, typeMask, docDomain, thirdParty, siteke y, specificOnly) 393 matchesAnyInternal(location, typeMask, docDomain, thirdParty, sitekey,
394 specificOnly)
357 { 395 {
358 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g); 396 let candidates = location.toLowerCase().match(/[a-z0-9%]{3,}/g);
359 if (candidates === null) 397 if (candidates === null)
360 candidates = []; 398 candidates = [];
361 candidates.push(""); 399 candidates.push("");
362 400
363 let blacklistHit = null; 401 let blacklistHit = null;
364 for (let i = 0, l = candidates.length; i < l; i++) 402 for (let i = 0, l = candidates.length; i < l; i++)
365 { 403 {
366 let substr = candidates[i]; 404 let substr = candidates[i];
367 if (substr in this.whitelist.filterByKeyword) 405 if (substr in this.whitelist.filterByKeyword)
368 { 406 {
369 let result = this.whitelist._checkEntryMatch(substr, location, typeMask, docDomain, thirdParty, sitekey); 407 let result = this.whitelist._checkEntryMatch(
408 substr, location, typeMask, docDomain, thirdParty, sitekey
409 );
370 if (result) 410 if (result)
371 return result; 411 return result;
372 } 412 }
373 if (substr in this.blacklist.filterByKeyword && blacklistHit === null) 413 if (substr in this.blacklist.filterByKeyword && blacklistHit === null)
374 blacklistHit = this.blacklist._checkEntryMatch(substr, location, typeMas k, docDomain, thirdParty, sitekey, specificOnly); 414 {
415 blacklistHit = this.blacklist._checkEntryMatch(
416 substr, location, typeMask, docDomain, thirdParty, sitekey,
417 specificOnly
418 );
419 }
375 } 420 }
376 return blacklistHit; 421 return blacklistHit;
377 }, 422 },
378 423
379 /** 424 /**
380 * @see Matcher#matchesAny 425 * @see Matcher#matchesAny
426 * @inheritdoc
381 */ 427 */
382 matchesAny: function(location, typeMask, docDomain, thirdParty, sitekey, speci ficOnly) 428 matchesAny(location, typeMask, docDomain, thirdParty, sitekey, specificOnly)
383 { 429 {
384 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty + " " + sitekey + " " + specificOnly; 430 let key = location + " " + typeMask + " " + docDomain + " " + thirdParty +
431 " " + sitekey + " " + specificOnly;
385 if (key in this.resultCache) 432 if (key in this.resultCache)
386 return this.resultCache[key]; 433 return this.resultCache[key];
387 434
388 let result = this.matchesAnyInternal(location, typeMask, docDomain, thirdPar ty, sitekey, specificOnly); 435 let result = this.matchesAnyInternal(location, typeMask, docDomain,
436 thirdParty, sitekey, specificOnly);
389 437
390 if (this.cacheEntries >= CombinedMatcher.maxCacheEntries) 438 if (this.cacheEntries >= CombinedMatcher.maxCacheEntries)
391 { 439 {
392 this.resultCache = Object.create(null); 440 this.resultCache = Object.create(null);
393 this.cacheEntries = 0; 441 this.cacheEntries = 0;
394 } 442 }
395 443
396 this.resultCache[key] = result; 444 this.resultCache[key] = result;
397 this.cacheEntries++; 445 this.cacheEntries++;
398 446
399 return result; 447 return result;
400 } 448 }
401 } 449 };
402 450
403 /** 451 /**
404 * Shared CombinedMatcher instance that should usually be used. 452 * Shared CombinedMatcher instance that should usually be used.
405 * @type CombinedMatcher 453 * @type {CombinedMatcher}
406 */ 454 */
407 let defaultMatcher = exports.defaultMatcher = new CombinedMatcher(); 455 exports.defaultMatcher = new CombinedMatcher();
OLDNEW
« no previous file with comments | « lib/filterStorage.js ('k') | lib/notification.js » ('j') | lib/synchronizer.js » ('J')

Powered by Google App Engine
This is Rietveld