@adguard/agtree 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agtree.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v2.1.2 (build date: Thu, 19 Sep 2024 15:44:40 GMT)
2
+ * AGTree v2.1.4 (build date: Mon, 25 Nov 2024 16:56:07 GMT)
3
3
  * (c) 2024 Adguard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -2357,9 +2357,11 @@ declare class FilterListParser extends ParserBase {
2357
2357
  * @param ast AST to generate
2358
2358
  * @param preferRaw If `true`, then the parser will use `raws.text` property of each rule
2359
2359
  * if it is available. Default is `false`.
2360
+ * @param tolerant If `true`, errors during rule generation will be logged to the console and invalid rules
2361
+ * will be skipped. If `false`, an error will be thrown on the first invalid rule. Default is `true`.
2360
2362
  * @returns Serialized filter list
2361
2363
  */
2362
- static generate(ast: FilterList, preferRaw?: boolean): string;
2364
+ static generate(ast: FilterList, preferRaw?: boolean, tolerant?: boolean): string;
2363
2365
  /**
2364
2366
  * Serializes a filter list node to binary format.
2365
2367
  *
package/dist/agtree.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v2.1.2 (build date: Thu, 19 Sep 2024 15:44:40 GMT)
2
+ * AGTree v2.1.4 (build date: Mon, 25 Nov 2024 16:56:07 GMT)
3
3
  * (c) 2024 Adguard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -343,10 +343,11 @@ class StringUtils {
343
343
  * @param searchedCharacter - Searched character
344
344
  * @param start - Start index
345
345
  * @param escapeCharacter - Escape character, \ by default
346
+ * @param end - End index (excluded)
346
347
  * @returns Index or -1 if the character not found
347
348
  */
348
- static findNextUnescapedCharacter(pattern, searchedCharacter, start = 0, escapeCharacter = ESCAPE_CHARACTER) {
349
- for (let i = start; i < pattern.length; i += 1) {
349
+ static findNextUnescapedCharacter(pattern, searchedCharacter, start = 0, escapeCharacter = ESCAPE_CHARACTER, end = pattern.length) {
350
+ for (let i = start; i < end; i += 1) {
350
351
  // The searched character cannot be preceded by an escape
351
352
  if (pattern[i] === searchedCharacter && pattern[i - 1] !== escapeCharacter) {
352
353
  return i;
@@ -361,10 +362,11 @@ class StringUtils {
361
362
  * @param searchedCharacter - Searched character
362
363
  * @param start - Start index
363
364
  * @param escapeCharacter - Escape character, \ by default
365
+ * @param end - End index (Included)
364
366
  * @returns Index or -1 if the character not found
365
367
  */
366
- static findNextUnescapedCharacterBackwards(pattern, searchedCharacter, start = pattern.length - 1, escapeCharacter = ESCAPE_CHARACTER) {
367
- for (let i = start; i >= 0; i -= 1) {
368
+ static findNextUnescapedCharacterBackwards(pattern, searchedCharacter, start = pattern.length - 1, escapeCharacter = ESCAPE_CHARACTER, end = 0) {
369
+ for (let i = start; i >= end; i -= 1) {
368
370
  // The searched character cannot be preceded by an escape
369
371
  if (pattern[i] === searchedCharacter && pattern[i - 1] !== escapeCharacter) {
370
372
  return i;
@@ -1911,6 +1913,9 @@ class AgentCommentRuleParser extends ParserBase {
1911
1913
  static serialize(node, buffer) {
1912
1914
  buffer.writeUint8(BinaryTypeMap.AgentRuleNode);
1913
1915
  const count = node.children.length;
1916
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
1917
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
1918
+ // if there are no children in the binary data.
1914
1919
  if (count) {
1915
1920
  buffer.writeUint8(AgentRuleSerializationMap.Children);
1916
1921
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -1965,6 +1970,11 @@ class AgentCommentRuleParser extends ParserBase {
1965
1970
  }
1966
1971
  prop = buffer.readUint8();
1967
1972
  }
1973
+ // Maybe children are not present in the binary data,
1974
+ // in this case, we should initialize it as an empty array.
1975
+ if (!node.children) {
1976
+ node.children = [];
1977
+ }
1968
1978
  }
1969
1979
  }
1970
1980
 
@@ -2860,6 +2870,9 @@ class HintCommentRuleParser extends ParserBase {
2860
2870
  buffer.writeUint8(SYNTAX_SERIALIZATION_MAP.get(exports.AdblockSyntax.Adg) ?? 0);
2861
2871
  }
2862
2872
  const count = node.children.length;
2873
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
2874
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
2875
+ // if there are no children in the binary data.
2863
2876
  if (count) {
2864
2877
  buffer.writeUint8(HintRuleSerializationMap.Children);
2865
2878
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -2913,6 +2926,11 @@ class HintCommentRuleParser extends ParserBase {
2913
2926
  }
2914
2927
  prop = buffer.readUint8();
2915
2928
  }
2929
+ // Maybe children are not present in the binary data,
2930
+ // in this case, we should initialize it as an empty array.
2931
+ if (!node.children) {
2932
+ node.children = [];
2933
+ }
2916
2934
  }
2917
2935
  }
2918
2936
 
@@ -5346,6 +5364,9 @@ class ModifierListParser extends ParserBase {
5346
5364
  static serialize(node, buffer) {
5347
5365
  buffer.writeUint8(BinaryTypeMap.ModifierListNode);
5348
5366
  const count = node.children.length;
5367
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
5368
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
5369
+ // if there are no children in the binary data.
5349
5370
  if (count) {
5350
5371
  buffer.writeUint8(ModifierListNodeSerializationMap.Children);
5351
5372
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -5397,6 +5418,11 @@ class ModifierListParser extends ParserBase {
5397
5418
  }
5398
5419
  prop = buffer.readUint8();
5399
5420
  }
5421
+ // Maybe children are not present in the binary data,
5422
+ // in this case, we should initialize it as an empty array.
5423
+ if (!node.children) {
5424
+ node.children = [];
5425
+ }
5400
5426
  }
5401
5427
  }
5402
5428
 
@@ -7052,9 +7078,6 @@ class UboParameterListParser extends ParameterListParser {
7052
7078
  // Next non-whitespace character after the closing quote should be the separator
7053
7079
  const nextSeparatorIndex = StringUtils.skipWS(raw, possibleClosingQuoteIndex + 1);
7054
7080
  if (nextSeparatorIndex === length) {
7055
- if (requireQuotes) {
7056
- throw new AdblockSyntaxError('Expected separator, got end of string', baseOffset + nextSeparatorIndex, baseOffset + length);
7057
- }
7058
7081
  // If the separator is not found, the param end is the end of the string
7059
7082
  paramEnd = StringUtils.skipWSBack(raw, length - 1) + 1;
7060
7083
  offset = length;
@@ -7068,9 +7091,41 @@ class UboParameterListParser extends ParameterListParser {
7068
7091
  if (requireQuotes) {
7069
7092
  throw new AdblockSyntaxError(`Expected separator, got: '${raw[nextSeparatorIndex]}'`, baseOffset + nextSeparatorIndex, baseOffset + length);
7070
7093
  }
7071
- // Param end should be the last separator before the quote
7072
- offset = StringUtils.findNextUnescapedCharacterBackwards(raw, separator, possibleClosingQuoteIndex) + 1;
7073
- paramEnd = StringUtils.skipWSBack(raw, offset - 2) + 1;
7094
+ /**
7095
+ * At that point found `possibleClosingQuoteIndex` is wrong
7096
+ * | is `offset`
7097
+ * ~ is `possibleClosingQuoteIndex`
7098
+ * ^ is `nextSeparatorIndex`
7099
+ *
7100
+ * Example 1: "abc, ').cba='1'"
7101
+ * | ~^
7102
+ * Example 2: "abc, ').cba, '1'"
7103
+ * | ~^
7104
+ * Example 3: "abc, ').cba='1', cba"
7105
+ * | ~^
7106
+ *
7107
+ * Search for separator before `possibleClosingQuoteIndex`
7108
+ */
7109
+ const separatorIndexBeforeQuote = StringUtils.findNextUnescapedCharacterBackwards(raw, separator, possibleClosingQuoteIndex, ESCAPE_CHARACTER, offset + 1);
7110
+ if (separatorIndexBeforeQuote !== -1) {
7111
+ // Found separator before (Example 2)
7112
+ paramEnd = StringUtils.skipWSBack(raw, separatorIndexBeforeQuote - 1) + 1;
7113
+ offset = separatorIndexBeforeQuote + 1;
7114
+ }
7115
+ else {
7116
+ // Didn't found separator before, search after
7117
+ const separatorIndexAfterQuote = StringUtils.findNextUnescapedCharacter(raw, separator, possibleClosingQuoteIndex);
7118
+ if (separatorIndexAfterQuote !== -1) {
7119
+ // We found separator after (Example 3)
7120
+ paramEnd = StringUtils.skipWSBack(raw, separatorIndexAfterQuote - 1) + 1;
7121
+ offset = separatorIndexAfterQuote + 1;
7122
+ }
7123
+ else {
7124
+ // If the separator is not found, the param end is the end of the string (Example 1)
7125
+ paramEnd = StringUtils.skipWSBack(raw, length - 1) + 1;
7126
+ offset = length;
7127
+ }
7128
+ }
7074
7129
  }
7075
7130
  }
7076
7131
  else {
@@ -8580,10 +8635,16 @@ class NetworkRuleParser extends ParserBase {
8580
8635
  const pattern = ValueParser.parse(raw.slice(patternStart, patternEnd), options, baseOffset + patternStart);
8581
8636
  // Parse modifiers (if any)
8582
8637
  let modifiers;
8638
+ // Get a last non-whitespace index
8639
+ const lastNonWsIndex = StringUtils.skipWSBack(raw);
8583
8640
  // Find start and end index of the modifiers
8584
8641
  const modifiersStart = separatorIndex + 1;
8585
- const modifiersEnd = StringUtils.skipWSBack(raw) + 1;
8642
+ const modifiersEnd = lastNonWsIndex + 1;
8586
8643
  if (separatorIndex !== -1) {
8644
+ // Check for empty modifiers
8645
+ if (separatorIndex === lastNonWsIndex) {
8646
+ throw new AdblockSyntaxError('Empty modifiers are not allowed', baseOffset + separatorIndex, baseOffset + raw.length);
8647
+ }
8587
8648
  modifiers = ModifierListParser.parse(raw.slice(modifiersStart, modifiersEnd), options, baseOffset + modifiersStart);
8588
8649
  }
8589
8650
  // Throw error if there is no pattern and no modifiers
@@ -8902,6 +8963,9 @@ class HostRuleParser extends ParserBase {
8902
8963
  buffer.writeUint32(node.end);
8903
8964
  }
8904
8965
  const count = node.children.length;
8966
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
8967
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
8968
+ // if there are no children in the binary data.
8905
8969
  if (count) {
8906
8970
  // note: we store the count, because re-construction of the array is faster if we know the length
8907
8971
  if (count > UINT16_MAX) {
@@ -8945,6 +9009,11 @@ class HostRuleParser extends ParserBase {
8945
9009
  }
8946
9010
  prop = buffer.readUint8();
8947
9011
  }
9012
+ // Maybe children are not present in the binary data,
9013
+ // in this case, we should initialize it as an empty array.
9014
+ if (!node.children) {
9015
+ node.children = [];
9016
+ }
8948
9017
  }
8949
9018
  /**
8950
9019
  * Serializes a host rule node to binary format.
@@ -9712,9 +9781,11 @@ class FilterListParser extends ParserBase {
9712
9781
  * @param ast AST to generate
9713
9782
  * @param preferRaw If `true`, then the parser will use `raws.text` property of each rule
9714
9783
  * if it is available. Default is `false`.
9784
+ * @param tolerant If `true`, errors during rule generation will be logged to the console and invalid rules
9785
+ * will be skipped. If `false`, an error will be thrown on the first invalid rule. Default is `true`.
9715
9786
  * @returns Serialized filter list
9716
9787
  */
9717
- static generate(ast, preferRaw = false) {
9788
+ static generate(ast, preferRaw = false, tolerant = true) {
9718
9789
  let result = EMPTY;
9719
9790
  for (let i = 0; i < ast.children.length; i += 1) {
9720
9791
  const rule = ast.children[i];
@@ -9722,7 +9793,18 @@ class FilterListParser extends ParserBase {
9722
9793
  result += rule.raws.text;
9723
9794
  }
9724
9795
  else {
9725
- result += RuleParser.generate(rule);
9796
+ try {
9797
+ result += RuleParser.generate(rule);
9798
+ }
9799
+ catch (error) {
9800
+ if (tolerant) {
9801
+ // eslint-disable-next-line no-console
9802
+ console.error(`Error when generating: ${error}`);
9803
+ }
9804
+ else {
9805
+ throw new Error(String(error));
9806
+ }
9807
+ }
9726
9808
  }
9727
9809
  switch (rule.raws?.nl) {
9728
9810
  case 'crlf':
@@ -14669,7 +14751,7 @@ class RawFilterListConverter extends ConverterBase {
14669
14751
  return createConversionResult(rawFilterList, false);
14670
14752
  }
14671
14753
  // Otherwise, serialize the filter list and return the result
14672
- return createConversionResult(FilterListParser.generate(conversionResult.result), true);
14754
+ return createConversionResult(FilterListParser.generate(conversionResult.result, false, tolerant), true);
14673
14755
  }
14674
14756
  }
14675
14757
 
@@ -15617,7 +15699,7 @@ class RuleCategorizer {
15617
15699
  }
15618
15700
  }
15619
15701
 
15620
- const version = "2.1.2";
15702
+ const version = "2.1.4";
15621
15703
 
15622
15704
  /**
15623
15705
  * @file AGTree version
package/dist/agtree.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * AGTree v2.1.2 (build date: Thu, 19 Sep 2024 15:44:40 GMT)
2
+ * AGTree v2.1.4 (build date: Mon, 25 Nov 2024 16:56:07 GMT)
3
3
  * (c) 2024 Adguard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
@@ -323,10 +323,11 @@ class StringUtils {
323
323
  * @param searchedCharacter - Searched character
324
324
  * @param start - Start index
325
325
  * @param escapeCharacter - Escape character, \ by default
326
+ * @param end - End index (excluded)
326
327
  * @returns Index or -1 if the character not found
327
328
  */
328
- static findNextUnescapedCharacter(pattern, searchedCharacter, start = 0, escapeCharacter = ESCAPE_CHARACTER) {
329
- for (let i = start; i < pattern.length; i += 1) {
329
+ static findNextUnescapedCharacter(pattern, searchedCharacter, start = 0, escapeCharacter = ESCAPE_CHARACTER, end = pattern.length) {
330
+ for (let i = start; i < end; i += 1) {
330
331
  // The searched character cannot be preceded by an escape
331
332
  if (pattern[i] === searchedCharacter && pattern[i - 1] !== escapeCharacter) {
332
333
  return i;
@@ -341,10 +342,11 @@ class StringUtils {
341
342
  * @param searchedCharacter - Searched character
342
343
  * @param start - Start index
343
344
  * @param escapeCharacter - Escape character, \ by default
345
+ * @param end - End index (Included)
344
346
  * @returns Index or -1 if the character not found
345
347
  */
346
- static findNextUnescapedCharacterBackwards(pattern, searchedCharacter, start = pattern.length - 1, escapeCharacter = ESCAPE_CHARACTER) {
347
- for (let i = start; i >= 0; i -= 1) {
348
+ static findNextUnescapedCharacterBackwards(pattern, searchedCharacter, start = pattern.length - 1, escapeCharacter = ESCAPE_CHARACTER, end = 0) {
349
+ for (let i = start; i >= end; i -= 1) {
348
350
  // The searched character cannot be preceded by an escape
349
351
  if (pattern[i] === searchedCharacter && pattern[i - 1] !== escapeCharacter) {
350
352
  return i;
@@ -1891,6 +1893,9 @@ class AgentCommentRuleParser extends ParserBase {
1891
1893
  static serialize(node, buffer) {
1892
1894
  buffer.writeUint8(BinaryTypeMap.AgentRuleNode);
1893
1895
  const count = node.children.length;
1896
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
1897
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
1898
+ // if there are no children in the binary data.
1894
1899
  if (count) {
1895
1900
  buffer.writeUint8(AgentRuleSerializationMap.Children);
1896
1901
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -1945,6 +1950,11 @@ class AgentCommentRuleParser extends ParserBase {
1945
1950
  }
1946
1951
  prop = buffer.readUint8();
1947
1952
  }
1953
+ // Maybe children are not present in the binary data,
1954
+ // in this case, we should initialize it as an empty array.
1955
+ if (!node.children) {
1956
+ node.children = [];
1957
+ }
1948
1958
  }
1949
1959
  }
1950
1960
 
@@ -2840,6 +2850,9 @@ class HintCommentRuleParser extends ParserBase {
2840
2850
  buffer.writeUint8(SYNTAX_SERIALIZATION_MAP.get(AdblockSyntax.Adg) ?? 0);
2841
2851
  }
2842
2852
  const count = node.children.length;
2853
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
2854
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
2855
+ // if there are no children in the binary data.
2843
2856
  if (count) {
2844
2857
  buffer.writeUint8(HintRuleSerializationMap.Children);
2845
2858
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -2893,6 +2906,11 @@ class HintCommentRuleParser extends ParserBase {
2893
2906
  }
2894
2907
  prop = buffer.readUint8();
2895
2908
  }
2909
+ // Maybe children are not present in the binary data,
2910
+ // in this case, we should initialize it as an empty array.
2911
+ if (!node.children) {
2912
+ node.children = [];
2913
+ }
2896
2914
  }
2897
2915
  }
2898
2916
 
@@ -5326,6 +5344,9 @@ class ModifierListParser extends ParserBase {
5326
5344
  static serialize(node, buffer) {
5327
5345
  buffer.writeUint8(BinaryTypeMap.ModifierListNode);
5328
5346
  const count = node.children.length;
5347
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
5348
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
5349
+ // if there are no children in the binary data.
5329
5350
  if (count) {
5330
5351
  buffer.writeUint8(ModifierListNodeSerializationMap.Children);
5331
5352
  // note: we store the count, because re-construction of the array is faster if we know the length
@@ -5377,6 +5398,11 @@ class ModifierListParser extends ParserBase {
5377
5398
  }
5378
5399
  prop = buffer.readUint8();
5379
5400
  }
5401
+ // Maybe children are not present in the binary data,
5402
+ // in this case, we should initialize it as an empty array.
5403
+ if (!node.children) {
5404
+ node.children = [];
5405
+ }
5380
5406
  }
5381
5407
  }
5382
5408
 
@@ -7032,9 +7058,6 @@ class UboParameterListParser extends ParameterListParser {
7032
7058
  // Next non-whitespace character after the closing quote should be the separator
7033
7059
  const nextSeparatorIndex = StringUtils.skipWS(raw, possibleClosingQuoteIndex + 1);
7034
7060
  if (nextSeparatorIndex === length) {
7035
- if (requireQuotes) {
7036
- throw new AdblockSyntaxError('Expected separator, got end of string', baseOffset + nextSeparatorIndex, baseOffset + length);
7037
- }
7038
7061
  // If the separator is not found, the param end is the end of the string
7039
7062
  paramEnd = StringUtils.skipWSBack(raw, length - 1) + 1;
7040
7063
  offset = length;
@@ -7048,9 +7071,41 @@ class UboParameterListParser extends ParameterListParser {
7048
7071
  if (requireQuotes) {
7049
7072
  throw new AdblockSyntaxError(`Expected separator, got: '${raw[nextSeparatorIndex]}'`, baseOffset + nextSeparatorIndex, baseOffset + length);
7050
7073
  }
7051
- // Param end should be the last separator before the quote
7052
- offset = StringUtils.findNextUnescapedCharacterBackwards(raw, separator, possibleClosingQuoteIndex) + 1;
7053
- paramEnd = StringUtils.skipWSBack(raw, offset - 2) + 1;
7074
+ /**
7075
+ * At that point found `possibleClosingQuoteIndex` is wrong
7076
+ * | is `offset`
7077
+ * ~ is `possibleClosingQuoteIndex`
7078
+ * ^ is `nextSeparatorIndex`
7079
+ *
7080
+ * Example 1: "abc, ').cba='1'"
7081
+ * | ~^
7082
+ * Example 2: "abc, ').cba, '1'"
7083
+ * | ~^
7084
+ * Example 3: "abc, ').cba='1', cba"
7085
+ * | ~^
7086
+ *
7087
+ * Search for separator before `possibleClosingQuoteIndex`
7088
+ */
7089
+ const separatorIndexBeforeQuote = StringUtils.findNextUnescapedCharacterBackwards(raw, separator, possibleClosingQuoteIndex, ESCAPE_CHARACTER, offset + 1);
7090
+ if (separatorIndexBeforeQuote !== -1) {
7091
+ // Found separator before (Example 2)
7092
+ paramEnd = StringUtils.skipWSBack(raw, separatorIndexBeforeQuote - 1) + 1;
7093
+ offset = separatorIndexBeforeQuote + 1;
7094
+ }
7095
+ else {
7096
+ // Didn't found separator before, search after
7097
+ const separatorIndexAfterQuote = StringUtils.findNextUnescapedCharacter(raw, separator, possibleClosingQuoteIndex);
7098
+ if (separatorIndexAfterQuote !== -1) {
7099
+ // We found separator after (Example 3)
7100
+ paramEnd = StringUtils.skipWSBack(raw, separatorIndexAfterQuote - 1) + 1;
7101
+ offset = separatorIndexAfterQuote + 1;
7102
+ }
7103
+ else {
7104
+ // If the separator is not found, the param end is the end of the string (Example 1)
7105
+ paramEnd = StringUtils.skipWSBack(raw, length - 1) + 1;
7106
+ offset = length;
7107
+ }
7108
+ }
7054
7109
  }
7055
7110
  }
7056
7111
  else {
@@ -8560,10 +8615,16 @@ class NetworkRuleParser extends ParserBase {
8560
8615
  const pattern = ValueParser.parse(raw.slice(patternStart, patternEnd), options, baseOffset + patternStart);
8561
8616
  // Parse modifiers (if any)
8562
8617
  let modifiers;
8618
+ // Get a last non-whitespace index
8619
+ const lastNonWsIndex = StringUtils.skipWSBack(raw);
8563
8620
  // Find start and end index of the modifiers
8564
8621
  const modifiersStart = separatorIndex + 1;
8565
- const modifiersEnd = StringUtils.skipWSBack(raw) + 1;
8622
+ const modifiersEnd = lastNonWsIndex + 1;
8566
8623
  if (separatorIndex !== -1) {
8624
+ // Check for empty modifiers
8625
+ if (separatorIndex === lastNonWsIndex) {
8626
+ throw new AdblockSyntaxError('Empty modifiers are not allowed', baseOffset + separatorIndex, baseOffset + raw.length);
8627
+ }
8567
8628
  modifiers = ModifierListParser.parse(raw.slice(modifiersStart, modifiersEnd), options, baseOffset + modifiersStart);
8568
8629
  }
8569
8630
  // Throw error if there is no pattern and no modifiers
@@ -8882,6 +8943,9 @@ class HostRuleParser extends ParserBase {
8882
8943
  buffer.writeUint32(node.end);
8883
8944
  }
8884
8945
  const count = node.children.length;
8946
+ // If there are no children, we do not write any data related to them, to avoid using unnecessary storage,
8947
+ // but children is a required field, so during deserialization we should initialize it as an empty array,
8948
+ // if there are no children in the binary data.
8885
8949
  if (count) {
8886
8950
  // note: we store the count, because re-construction of the array is faster if we know the length
8887
8951
  if (count > UINT16_MAX) {
@@ -8925,6 +8989,11 @@ class HostRuleParser extends ParserBase {
8925
8989
  }
8926
8990
  prop = buffer.readUint8();
8927
8991
  }
8992
+ // Maybe children are not present in the binary data,
8993
+ // in this case, we should initialize it as an empty array.
8994
+ if (!node.children) {
8995
+ node.children = [];
8996
+ }
8928
8997
  }
8929
8998
  /**
8930
8999
  * Serializes a host rule node to binary format.
@@ -9692,9 +9761,11 @@ class FilterListParser extends ParserBase {
9692
9761
  * @param ast AST to generate
9693
9762
  * @param preferRaw If `true`, then the parser will use `raws.text` property of each rule
9694
9763
  * if it is available. Default is `false`.
9764
+ * @param tolerant If `true`, errors during rule generation will be logged to the console and invalid rules
9765
+ * will be skipped. If `false`, an error will be thrown on the first invalid rule. Default is `true`.
9695
9766
  * @returns Serialized filter list
9696
9767
  */
9697
- static generate(ast, preferRaw = false) {
9768
+ static generate(ast, preferRaw = false, tolerant = true) {
9698
9769
  let result = EMPTY;
9699
9770
  for (let i = 0; i < ast.children.length; i += 1) {
9700
9771
  const rule = ast.children[i];
@@ -9702,7 +9773,18 @@ class FilterListParser extends ParserBase {
9702
9773
  result += rule.raws.text;
9703
9774
  }
9704
9775
  else {
9705
- result += RuleParser.generate(rule);
9776
+ try {
9777
+ result += RuleParser.generate(rule);
9778
+ }
9779
+ catch (error) {
9780
+ if (tolerant) {
9781
+ // eslint-disable-next-line no-console
9782
+ console.error(`Error when generating: ${error}`);
9783
+ }
9784
+ else {
9785
+ throw new Error(String(error));
9786
+ }
9787
+ }
9706
9788
  }
9707
9789
  switch (rule.raws?.nl) {
9708
9790
  case 'crlf':
@@ -14649,7 +14731,7 @@ class RawFilterListConverter extends ConverterBase {
14649
14731
  return createConversionResult(rawFilterList, false);
14650
14732
  }
14651
14733
  // Otherwise, serialize the filter list and return the result
14652
- return createConversionResult(FilterListParser.generate(conversionResult.result), true);
14734
+ return createConversionResult(FilterListParser.generate(conversionResult.result, false, tolerant), true);
14653
14735
  }
14654
14736
  }
14655
14737
 
@@ -15597,7 +15679,7 @@ class RuleCategorizer {
15597
15679
  }
15598
15680
  }
15599
15681
 
15600
- const version = "2.1.2";
15682
+ const version = "2.1.4";
15601
15683
 
15602
15684
  /**
15603
15685
  * @file AGTree version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adguard/agtree",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "Tool set for working with adblock filter lists",
5
5
  "keywords": [
6
6
  "adblock",