@adguard/agtree 3.4.1 → 4.0.0

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.
Files changed (421) hide show
  1. package/dist/ast-utils/clone.js +2 -2
  2. package/dist/ast-utils/modifiers.js +2 -2
  3. package/dist/ast-utils/network-rules.js +2 -2
  4. package/dist/ast-utils/scriptlets.js +2 -2
  5. package/dist/common/abp-snippet-injection-body-common.js +2 -2
  6. package/dist/common/agent-common.js +2 -2
  7. package/dist/common/ubo-html-filtering-body-common.js +40 -0
  8. package/dist/common/ubo-selector-common.js +2 -2
  9. package/dist/compatibility-tables/base.js +2 -2
  10. package/dist/compatibility-tables/compatibility-table-data.js +4 -4
  11. package/dist/compatibility-tables/modifiers.js +2 -2
  12. package/dist/compatibility-tables/platforms.js +2 -2
  13. package/dist/compatibility-tables/redirects.js +2 -2
  14. package/dist/compatibility-tables/schemas/base.js +2 -2
  15. package/dist/compatibility-tables/schemas/modifier.js +2 -2
  16. package/dist/compatibility-tables/schemas/platform.js +2 -2
  17. package/dist/compatibility-tables/schemas/redirect.js +2 -2
  18. package/dist/compatibility-tables/schemas/resource-type.js +2 -2
  19. package/dist/compatibility-tables/schemas/scriptlet.js +2 -2
  20. package/dist/compatibility-tables/scriptlets.js +2 -2
  21. package/dist/compatibility-tables/utils/platform-helpers.js +2 -2
  22. package/dist/compatibility-tables/utils/resource-type-helpers.js +2 -2
  23. package/dist/compatibility-tables/utils/zod-camelcase.js +2 -2
  24. package/dist/converter/base-interfaces/base-converter.js +2 -2
  25. package/dist/converter/base-interfaces/conversion-result.js +2 -2
  26. package/dist/converter/base-interfaces/rule-converter-base.js +2 -2
  27. package/dist/converter/comment/index.js +2 -2
  28. package/dist/converter/cosmetic/css.js +2 -2
  29. package/dist/converter/cosmetic/element-hiding.js +2 -2
  30. package/dist/converter/cosmetic/header-removal.js +24 -42
  31. package/dist/converter/cosmetic/html.js +580 -438
  32. package/dist/converter/cosmetic/index.js +3 -2
  33. package/dist/converter/cosmetic/rule-modifiers/adg.js +2 -2
  34. package/dist/converter/cosmetic/rule-modifiers/ubo.js +2 -2
  35. package/dist/converter/cosmetic/scriptlet.js +2 -2
  36. package/dist/converter/css/index.js +3 -2
  37. package/dist/converter/data/css.js +32 -54
  38. package/dist/converter/filter-list.js +2 -2
  39. package/dist/converter/index.js +2 -2
  40. package/dist/converter/misc/network-rule-modifier.js +2 -2
  41. package/dist/converter/network/index.js +2 -2
  42. package/dist/converter/raw-filter-list.js +2 -2
  43. package/dist/converter/raw-rule.js +6 -3
  44. package/dist/converter/rule.js +2 -2
  45. package/dist/errors/adblock-syntax-error.js +2 -2
  46. package/dist/errors/binary-schema-mismatch-error.js +2 -2
  47. package/dist/errors/not-implemented-error.js +2 -2
  48. package/dist/errors/rule-conversion-error.js +2 -2
  49. package/dist/generator/base-generator.js +2 -2
  50. package/dist/generator/comment/agent-comment-generator.js +2 -2
  51. package/dist/generator/comment/agent-generator.js +2 -2
  52. package/dist/generator/comment/comment-rule-generator.js +2 -2
  53. package/dist/generator/comment/config-comment-generator.js +2 -2
  54. package/dist/generator/comment/hint-comment-generator.js +2 -2
  55. package/dist/generator/comment/hint-generator.js +2 -2
  56. package/dist/generator/comment/metadata-comment-generator.js +2 -2
  57. package/dist/generator/comment/pre-processor-comment-generator.js +2 -2
  58. package/dist/generator/comment/simple-comment-generator.js +2 -2
  59. package/dist/generator/cosmetic/cosmetic-rule-body-generator.js +21 -6
  60. package/dist/generator/cosmetic/cosmetic-rule-generator.js +2 -2
  61. package/dist/generator/cosmetic/cosmetic-rule-pattern-generator.js +2 -2
  62. package/dist/generator/cosmetic/html-filtering-body/adg-html-filtering-body-generator.js +38 -0
  63. package/dist/generator/cosmetic/html-filtering-body/html-filtering-body-generator.js +32 -0
  64. package/dist/generator/cosmetic/html-filtering-body/ubo-html-filtering-body-generator.js +65 -0
  65. package/dist/generator/cosmetic/{body → scriptlet-body}/abp-snippet-injection-body-generator.js +2 -2
  66. package/dist/generator/cosmetic/{body → scriptlet-body}/adg-scriptlet-injection-body-generator.js +2 -2
  67. package/dist/generator/cosmetic/{body → scriptlet-body}/ubo-scriptlet-injection-body-generator.js +2 -2
  68. package/dist/generator/cosmetic/selector/attribute-selector-generator.js +42 -0
  69. package/dist/generator/cosmetic/selector/class-selector-generator.js +26 -0
  70. package/dist/generator/cosmetic/selector/complex-selector-generator.js +71 -0
  71. package/dist/generator/cosmetic/selector/id-selector-generator.js +26 -0
  72. package/dist/generator/cosmetic/selector/pseudo-class-selector-generator.js +35 -0
  73. package/dist/generator/cosmetic/selector/selector-combinator-generator.js +30 -0
  74. package/dist/generator/cosmetic/selector/selector-list-generator.js +41 -0
  75. package/dist/generator/cosmetic/selector/type-selector-generator.js +25 -0
  76. package/dist/generator/css/adg-css-injection-generator.js +5 -6
  77. package/dist/generator/filterlist-generator.js +2 -2
  78. package/dist/generator/index.js +2 -2
  79. package/dist/generator/misc/domain-list-generator.js +2 -2
  80. package/dist/generator/misc/list-items-generator.js +2 -2
  81. package/dist/generator/misc/logical-expression-generator.js +2 -2
  82. package/dist/generator/misc/modifier-generator.js +2 -2
  83. package/dist/generator/misc/modifier-list-generator.js +2 -2
  84. package/dist/generator/misc/parameter-list-generator.js +2 -2
  85. package/dist/generator/misc/value-generator.js +2 -2
  86. package/dist/generator/network/host-rule-generator.js +2 -2
  87. package/dist/generator/network/network-rule-generator.js +2 -2
  88. package/dist/generator/rule-generator.js +2 -2
  89. package/dist/index.js +4 -14
  90. package/dist/nodes/index.js +2 -2
  91. package/dist/package.json.js +3 -3
  92. package/dist/parser/base-parser.js +2 -2
  93. package/dist/parser/comment/agent-comment-parser.js +2 -2
  94. package/dist/parser/comment/agent-parser.js +2 -2
  95. package/dist/parser/comment/comment-parser.js +2 -2
  96. package/dist/parser/comment/config-comment-parser.js +2 -2
  97. package/dist/parser/comment/hint-comment-parser.js +2 -2
  98. package/dist/parser/comment/hint-parser.js +2 -2
  99. package/dist/parser/comment/metadata-comment-parser.js +2 -2
  100. package/dist/parser/comment/preprocessor-parser.js +2 -2
  101. package/dist/parser/comment/simple-comment-parser.js +2 -2
  102. package/dist/parser/cosmetic/cosmetic-rule-parser.js +10 -21
  103. package/dist/parser/cosmetic/html-filtering-body/adg-html-filtering-body-parser.js +49 -0
  104. package/dist/parser/cosmetic/html-filtering-body/html-filtering-body-parser.js +70 -0
  105. package/dist/parser/cosmetic/html-filtering-body/ubo-html-filtering-body-parser.js +180 -0
  106. package/dist/parser/cosmetic/{body → scriptlet-body}/abp-snippet-injection-body-parser.js +2 -2
  107. package/dist/parser/cosmetic/{body → scriptlet-body}/adg-scriptlet-injection-body-parser.js +2 -2
  108. package/dist/parser/cosmetic/{body → scriptlet-body}/ubo-scriptlet-injection-body-parser.js +2 -2
  109. package/dist/parser/cosmetic/selector/handlers/attribute-selector-handler.js +206 -0
  110. package/dist/parser/cosmetic/selector/handlers/class-selector-handler.js +48 -0
  111. package/dist/parser/cosmetic/selector/handlers/complex-selector-handler.js +67 -0
  112. package/dist/parser/cosmetic/selector/handlers/compound-selector-handler.js +97 -0
  113. package/dist/parser/cosmetic/selector/handlers/id-selector-handler.js +42 -0
  114. package/dist/parser/cosmetic/selector/handlers/pseudo-class-selector-handler.js +128 -0
  115. package/dist/parser/cosmetic/selector/handlers/type-selector-handler.js +58 -0
  116. package/dist/parser/cosmetic/selector/selector-list-parser.js +164 -0
  117. package/dist/parser/css/adg-css-injection-parser.js +4 -5
  118. package/dist/parser/css/balancing.js +2 -2
  119. package/dist/parser/css/constants.js +2 -2
  120. package/dist/parser/css/css-token-stream.js +41 -4
  121. package/dist/parser/css/ubo-selector-parser.js +2 -2
  122. package/dist/parser/filterlist-parser.js +2 -2
  123. package/dist/parser/index.js +2 -2
  124. package/dist/parser/misc/app-list-parser.js +2 -2
  125. package/dist/parser/misc/domain-list-parser.js +2 -2
  126. package/dist/parser/misc/list-items-parser.js +2 -2
  127. package/dist/parser/misc/logical-expression-parser.js +2 -2
  128. package/dist/parser/misc/method-list-parser.js +2 -2
  129. package/dist/parser/misc/modifier-list.js +9 -3
  130. package/dist/parser/misc/modifier-parser.js +2 -2
  131. package/dist/parser/misc/parameter-list-parser.js +2 -2
  132. package/dist/parser/misc/stealth-option-list-parser.js +2 -2
  133. package/dist/parser/misc/ubo-parameter-list-parser.js +2 -2
  134. package/dist/parser/misc/value-parser.js +2 -2
  135. package/dist/parser/network/host-rule-parser.js +2 -2
  136. package/dist/parser/network/network-rule-parser.js +2 -2
  137. package/dist/parser/options.js +3 -2
  138. package/dist/parser/rule-parser.js +2 -2
  139. package/dist/types/common/ubo-html-filtering-body-common.d.ts +12 -0
  140. package/dist/types/compatibility-tables/compatibility-table-data.d.ts +3 -3
  141. package/dist/types/compatibility-tables/schemas/redirect.d.ts +1 -1
  142. package/dist/types/compatibility-tables/schemas/scriptlet.d.ts +2 -2
  143. package/dist/types/converter/cosmetic/html.d.ts +167 -42
  144. package/dist/types/converter/data/css.d.ts +18 -6
  145. package/dist/types/generator/cosmetic/html-filtering-body/adg-html-filtering-body-generator.d.ts +17 -0
  146. package/dist/types/generator/cosmetic/html-filtering-body/html-filtering-body-generator.d.ts +17 -0
  147. package/dist/types/generator/cosmetic/html-filtering-body/ubo-html-filtering-body-generator.d.ts +30 -0
  148. package/dist/types/generator/cosmetic/selector/attribute-selector-generator.d.ts +15 -0
  149. package/dist/types/generator/cosmetic/selector/class-selector-generator.d.ts +15 -0
  150. package/dist/types/generator/cosmetic/selector/complex-selector-generator.d.ts +17 -0
  151. package/dist/types/generator/cosmetic/selector/id-selector-generator.d.ts +15 -0
  152. package/dist/types/generator/cosmetic/selector/pseudo-class-selector-generator.d.ts +15 -0
  153. package/dist/types/generator/cosmetic/selector/selector-combinator-generator.d.ts +15 -0
  154. package/dist/types/generator/cosmetic/selector/selector-list-generator.d.ts +17 -0
  155. package/dist/types/generator/cosmetic/selector/type-selector-generator.d.ts +15 -0
  156. package/dist/types/generator/css/adg-css-injection-generator.d.ts +1 -1
  157. package/dist/types/index.d.ts +3 -13
  158. package/dist/types/nodes/index.d.ts +159 -3
  159. package/dist/types/parser/cosmetic/html-filtering-body/adg-html-filtering-body-parser.d.ts +35 -0
  160. package/dist/types/parser/cosmetic/html-filtering-body/html-filtering-body-parser.d.ts +39 -0
  161. package/dist/types/parser/cosmetic/html-filtering-body/ubo-html-filtering-body-parser.d.ts +63 -0
  162. package/dist/types/parser/cosmetic/selector/context.d.ts +42 -0
  163. package/dist/types/parser/cosmetic/selector/handlers/attribute-selector-handler.d.ts +35 -0
  164. package/dist/types/parser/cosmetic/selector/handlers/class-selector-handler.d.ts +15 -0
  165. package/dist/types/parser/cosmetic/selector/handlers/complex-selector-handler.d.ts +20 -0
  166. package/dist/types/parser/cosmetic/selector/handlers/compound-selector-handler.d.ts +25 -0
  167. package/dist/types/parser/cosmetic/selector/handlers/id-selector-handler.d.ts +15 -0
  168. package/dist/types/parser/cosmetic/selector/handlers/pseudo-class-selector-handler.d.ts +15 -0
  169. package/dist/types/parser/cosmetic/selector/handlers/type-selector-handler.d.ts +15 -0
  170. package/dist/types/parser/cosmetic/selector/selector-list-parser.d.ts +38 -0
  171. package/dist/types/parser/css/adg-css-injection-parser.d.ts +0 -2
  172. package/dist/types/parser/css/css-token-stream.d.ts +18 -0
  173. package/dist/types/parser/network/network-rule-parser.d.ts +1 -1
  174. package/dist/types/parser/options.d.ts +5 -0
  175. package/dist/types/utils/constants.d.ts +1 -0
  176. package/dist/types/utils/index.d.ts +0 -6
  177. package/dist/types/utils/quotes.d.ts +44 -0
  178. package/dist/types/utils/regexp.d.ts +24 -0
  179. package/dist/utils/adblockers.js +2 -2
  180. package/dist/utils/bit-count.js +2 -2
  181. package/dist/utils/categorizer.js +2 -2
  182. package/dist/utils/clone.js +2 -2
  183. package/dist/utils/constants.js +6 -9
  184. package/dist/utils/cosmetic-rule-separator.js +2 -2
  185. package/dist/utils/deep-freeze.js +2 -2
  186. package/dist/utils/domain.js +2 -2
  187. package/dist/utils/error.js +2 -2
  188. package/dist/utils/index.js +2 -8
  189. package/dist/utils/logical-expression.js +2 -2
  190. package/dist/utils/multi-value-map.js +2 -2
  191. package/dist/utils/noop-modifier.js +2 -2
  192. package/dist/utils/position-provider.js +2 -2
  193. package/dist/utils/quotes.js +163 -10
  194. package/dist/utils/regexp.js +31 -2
  195. package/dist/utils/string.js +2 -2
  196. package/dist/utils/type-guards.js +3 -13
  197. package/dist/validator/constants.js +2 -2
  198. package/dist/validator/helpers.js +2 -2
  199. package/dist/validator/index.js +2 -2
  200. package/dist/validator/value.js +2 -2
  201. package/dist/version.js +2 -2
  202. package/package.json +8 -16
  203. package/dist/deserializer/base-deserializer.js +0 -29
  204. package/dist/deserializer/comment/agent-comment-deserializer.js +0 -79
  205. package/dist/deserializer/comment/agent-deserializer.js +0 -65
  206. package/dist/deserializer/comment/comment-rule-deserializer.js +0 -54
  207. package/dist/deserializer/comment/config-comment-deserializer.js +0 -111
  208. package/dist/deserializer/comment/hint-comment-deserializer.js +0 -72
  209. package/dist/deserializer/comment/hint-deserializer.js +0 -85
  210. package/dist/deserializer/comment/metadata-comment-deserializer.js +0 -69
  211. package/dist/deserializer/comment/pre-processor-comment-deserializer.js +0 -112
  212. package/dist/deserializer/comment/simple-comment-deserializer.js +0 -65
  213. package/dist/deserializer/cosmetic/cosmetic-rule-deserializer.js +0 -131
  214. package/dist/deserializer/cosmetic/css-injection-body-deserializer.js +0 -56
  215. package/dist/deserializer/cosmetic/element-hiding-body-deserializer.js +0 -48
  216. package/dist/deserializer/cosmetic/scriptlet-body/abp-snippet-injection-body-deserializer.js +0 -39
  217. package/dist/deserializer/cosmetic/scriptlet-body/adg-scriptlet-injection-body-deserializer.js +0 -40
  218. package/dist/deserializer/cosmetic/scriptlet-body/scriptlet-body-deserializer.js +0 -54
  219. package/dist/deserializer/cosmetic/scriptlet-body/ubo-scriptlet-injection-body-deserializer.js +0 -38
  220. package/dist/deserializer/empty-rule-deserializer.js +0 -48
  221. package/dist/deserializer/filterlist-deserializer.js +0 -85
  222. package/dist/deserializer/index.js +0 -8
  223. package/dist/deserializer/invalid-rule-deserializer.js +0 -50
  224. package/dist/deserializer/invalid-rule-error-node-deserializer.js +0 -50
  225. package/dist/deserializer/misc/domain-list-deserializer.js +0 -64
  226. package/dist/deserializer/misc/list-item-deserializer.js +0 -69
  227. package/dist/deserializer/misc/list-items-deserializer.js +0 -30
  228. package/dist/deserializer/misc/logical-expression-deserializer.js +0 -196
  229. package/dist/deserializer/misc/modifier-deserializer.js +0 -87
  230. package/dist/deserializer/misc/modifier-list-deserializer.js +0 -61
  231. package/dist/deserializer/misc/parameter-list-deserializer.js +0 -64
  232. package/dist/deserializer/misc/value-deserializer.js +0 -50
  233. package/dist/deserializer/network/host-rule-deserializer.js +0 -67
  234. package/dist/deserializer/network/hostname-list-deserializer.js +0 -56
  235. package/dist/deserializer/network/network-rule-deserializer.js +0 -65
  236. package/dist/deserializer/rule-deserializer.js +0 -65
  237. package/dist/deserializer/syntax-deserialization-map.js +0 -21
  238. package/dist/marshalling-utils/comment/agent-comment-common.js +0 -21
  239. package/dist/marshalling-utils/comment/agent-common.js +0 -40
  240. package/dist/marshalling-utils/comment/config-comment-common.js +0 -54
  241. package/dist/marshalling-utils/comment/hint-comment-common.js +0 -22
  242. package/dist/marshalling-utils/comment/hint-common.js +0 -56
  243. package/dist/marshalling-utils/comment/metadata-comment-common.js +0 -41
  244. package/dist/marshalling-utils/comment/pre-processor-comment-common.js +0 -59
  245. package/dist/marshalling-utils/comment/simple-comment-common.js +0 -22
  246. package/dist/marshalling-utils/cosmetic/body/abp-snippet-injection-body-common.js +0 -61
  247. package/dist/marshalling-utils/cosmetic/body/adg-scriptlet-injection-body-common.js +0 -66
  248. package/dist/marshalling-utils/cosmetic/body/css-injection-body-common.js +0 -24
  249. package/dist/marshalling-utils/cosmetic/body/element-hiding-body-common.js +0 -21
  250. package/dist/marshalling-utils/cosmetic/body/ubo-scriptlet-injection-body-common.js +0 -114
  251. package/dist/marshalling-utils/cosmetic/cosmetic-rule-common.js +0 -46
  252. package/dist/marshalling-utils/empty-rule-common.js +0 -20
  253. package/dist/marshalling-utils/filter-list-common.js +0 -21
  254. package/dist/marshalling-utils/invalid-rule-common.js +0 -21
  255. package/dist/marshalling-utils/invalid-rule-error-node-common.js +0 -22
  256. package/dist/marshalling-utils/misc/binary-type-common.js +0 -54
  257. package/dist/marshalling-utils/misc/domain-list-common.js +0 -36
  258. package/dist/marshalling-utils/misc/host-rule-common.js +0 -24
  259. package/dist/marshalling-utils/misc/hostname-list-common.js +0 -21
  260. package/dist/marshalling-utils/misc/list-item-common.js +0 -22
  261. package/dist/marshalling-utils/misc/logical-expression-common.js +0 -83
  262. package/dist/marshalling-utils/misc/modifier-common.js +0 -231
  263. package/dist/marshalling-utils/misc/modifier-list-common.js +0 -21
  264. package/dist/marshalling-utils/misc/parameter-list-common.js +0 -21
  265. package/dist/marshalling-utils/misc/value-common.js +0 -22
  266. package/dist/marshalling-utils/network/network-rule-common.js +0 -24
  267. package/dist/marshalling-utils/syntax-serialization-map.js +0 -30
  268. package/dist/serializer/base-serializer.js +0 -29
  269. package/dist/serializer/comment/agent-comment-serializer.js +0 -74
  270. package/dist/serializer/comment/agent-serializer.js +0 -59
  271. package/dist/serializer/comment/comment-rule-serializer.js +0 -105
  272. package/dist/serializer/comment/config-comment-serializer.js +0 -82
  273. package/dist/serializer/comment/hint-comment-serializer.js +0 -65
  274. package/dist/serializer/comment/hint-serializer.js +0 -54
  275. package/dist/serializer/comment/metadata-comment-serializer.js +0 -73
  276. package/dist/serializer/comment/pre-processor-comment-serializer.js +0 -71
  277. package/dist/serializer/comment/simple-comment-serializer.js +0 -52
  278. package/dist/serializer/cosmetic/body/abp-snippet-injection-body-serializer.js +0 -36
  279. package/dist/serializer/cosmetic/body/adg-scriptlet-injection-body-serializer.js +0 -36
  280. package/dist/serializer/cosmetic/body/scriptlet-body-serializer.js +0 -50
  281. package/dist/serializer/cosmetic/body/ubo-scriptlet-injection-body-serializer.js +0 -36
  282. package/dist/serializer/cosmetic/cosmetic-rule-serializer.js +0 -120
  283. package/dist/serializer/cosmetic/css-injection-body-serializer.js +0 -51
  284. package/dist/serializer/cosmetic/element-hiding-body-serializer.js +0 -40
  285. package/dist/serializer/empty-rule-serializer.js +0 -37
  286. package/dist/serializer/filterlist-serializer.js +0 -45
  287. package/dist/serializer/index.js +0 -7
  288. package/dist/serializer/invalid-rule-error-node-serializer.js +0 -41
  289. package/dist/serializer/invalid-rule-serializer.js +0 -40
  290. package/dist/serializer/misc/domain-list-serializer.js +0 -64
  291. package/dist/serializer/misc/list-item-serializer.js +0 -58
  292. package/dist/serializer/misc/list-items-serializer.js +0 -29
  293. package/dist/serializer/misc/logical-expression-serializer.js +0 -136
  294. package/dist/serializer/misc/modifier-list-serializer.js +0 -58
  295. package/dist/serializer/misc/modifier-serializer.js +0 -49
  296. package/dist/serializer/misc/parameter-list-serializer.js +0 -52
  297. package/dist/serializer/misc/value-serializer.js +0 -50
  298. package/dist/serializer/network/host-rule-serializer.js +0 -70
  299. package/dist/serializer/network/hostname-list-serializer.js +0 -53
  300. package/dist/serializer/network/network-rule-serializer.js +0 -54
  301. package/dist/serializer/rule-serializer.js +0 -61
  302. package/dist/types/deserializer/base-deserializer.d.ts +0 -15
  303. package/dist/types/deserializer/comment/agent-comment-deserializer.d.ts +0 -34
  304. package/dist/types/deserializer/comment/agent-deserializer.d.ts +0 -25
  305. package/dist/types/deserializer/comment/comment-rule-deserializer.d.ts +0 -16
  306. package/dist/types/deserializer/comment/config-comment-deserializer.d.ts +0 -27
  307. package/dist/types/deserializer/comment/hint-comment-deserializer.d.ts +0 -24
  308. package/dist/types/deserializer/comment/hint-deserializer.d.ts +0 -26
  309. package/dist/types/deserializer/comment/metadata-comment-deserializer.d.ts +0 -26
  310. package/dist/types/deserializer/comment/pre-processor-comment-deserializer.d.ts +0 -29
  311. package/dist/types/deserializer/comment/simple-comment-deserializer.d.ts +0 -26
  312. package/dist/types/deserializer/cosmetic/cosmetic-rule-deserializer.d.ts +0 -18
  313. package/dist/types/deserializer/cosmetic/css-injection-body-deserializer.d.ts +0 -15
  314. package/dist/types/deserializer/cosmetic/element-hiding-body-deserializer.d.ts +0 -16
  315. package/dist/types/deserializer/cosmetic/scriptlet-body/abp-snippet-injection-body-deserializer.d.ts +0 -17
  316. package/dist/types/deserializer/cosmetic/scriptlet-body/adg-scriptlet-injection-body-deserializer.d.ts +0 -18
  317. package/dist/types/deserializer/cosmetic/scriptlet-body/scriptlet-body-deserializer.d.ts +0 -19
  318. package/dist/types/deserializer/cosmetic/scriptlet-body/ubo-scriptlet-injection-body-deserializer.d.ts +0 -17
  319. package/dist/types/deserializer/empty-rule-deserializer.d.ts +0 -16
  320. package/dist/types/deserializer/filterlist-deserializer.d.ts +0 -34
  321. package/dist/types/deserializer/index.d.ts +0 -2
  322. package/dist/types/deserializer/invalid-rule-deserializer.d.ts +0 -16
  323. package/dist/types/deserializer/invalid-rule-error-node-deserializer.d.ts +0 -16
  324. package/dist/types/deserializer/misc/domain-list-deserializer.d.ts +0 -15
  325. package/dist/types/deserializer/misc/list-item-deserializer.d.ts +0 -19
  326. package/dist/types/deserializer/misc/list-items-deserializer.d.ts +0 -16
  327. package/dist/types/deserializer/misc/logical-expression-deserializer.d.ts +0 -55
  328. package/dist/types/deserializer/misc/modifier-deserializer.d.ts +0 -18
  329. package/dist/types/deserializer/misc/modifier-list-deserializer.d.ts +0 -20
  330. package/dist/types/deserializer/misc/parameter-list-deserializer.d.ts +0 -18
  331. package/dist/types/deserializer/misc/value-deserializer.d.ts +0 -17
  332. package/dist/types/deserializer/network/host-rule-deserializer.d.ts +0 -18
  333. package/dist/types/deserializer/network/hostname-list-deserializer.d.ts +0 -15
  334. package/dist/types/deserializer/network/network-rule-deserializer.d.ts +0 -18
  335. package/dist/types/deserializer/rule-deserializer.d.ts +0 -17
  336. package/dist/types/deserializer/syntax-deserialization-map.d.ts +0 -2
  337. package/dist/types/marshalling-utils/comment/agent-comment-common.d.ts +0 -14
  338. package/dist/types/marshalling-utils/comment/agent-common.d.ts +0 -20
  339. package/dist/types/marshalling-utils/comment/config-comment-common.d.ts +0 -42
  340. package/dist/types/marshalling-utils/comment/hint-comment-common.d.ts +0 -15
  341. package/dist/types/marshalling-utils/comment/hint-common.d.ts +0 -33
  342. package/dist/types/marshalling-utils/comment/metadata-comment-common.d.ts +0 -21
  343. package/dist/types/marshalling-utils/comment/pre-processor-comment-common.d.ts +0 -37
  344. package/dist/types/marshalling-utils/comment/simple-comment-common.d.ts +0 -15
  345. package/dist/types/marshalling-utils/cosmetic/body/abp-snippet-injection-body-common.d.ts +0 -23
  346. package/dist/types/marshalling-utils/cosmetic/body/adg-scriptlet-injection-body-common.d.ts +0 -9
  347. package/dist/types/marshalling-utils/cosmetic/body/css-injection-body-common.d.ts +0 -17
  348. package/dist/types/marshalling-utils/cosmetic/body/element-hiding-body-common.d.ts +0 -14
  349. package/dist/types/marshalling-utils/cosmetic/body/ubo-scriptlet-injection-body-common.d.ts +0 -9
  350. package/dist/types/marshalling-utils/cosmetic/cosmetic-rule-common.d.ts +0 -28
  351. package/dist/types/marshalling-utils/empty-rule-common.d.ts +0 -13
  352. package/dist/types/marshalling-utils/filter-list-common.d.ts +0 -14
  353. package/dist/types/marshalling-utils/invalid-rule-common.d.ts +0 -14
  354. package/dist/types/marshalling-utils/invalid-rule-error-node-common.d.ts +0 -15
  355. package/dist/types/marshalling-utils/misc/binary-type-common.d.ts +0 -57
  356. package/dist/types/marshalling-utils/misc/domain-list-common.d.ts +0 -24
  357. package/dist/types/marshalling-utils/misc/host-rule-common.d.ts +0 -18
  358. package/dist/types/marshalling-utils/misc/hostname-list-common.d.ts +0 -14
  359. package/dist/types/marshalling-utils/misc/list-item-common.d.ts +0 -15
  360. package/dist/types/marshalling-utils/misc/logical-expression-common.d.ts +0 -47
  361. package/dist/types/marshalling-utils/misc/modifier-common.d.ts +0 -41
  362. package/dist/types/marshalling-utils/misc/modifier-list-common.d.ts +0 -14
  363. package/dist/types/marshalling-utils/misc/parameter-list-common.d.ts +0 -14
  364. package/dist/types/marshalling-utils/misc/value-common.d.ts +0 -15
  365. package/dist/types/marshalling-utils/network/network-rule-common.d.ts +0 -18
  366. package/dist/types/marshalling-utils/syntax-serialization-map.d.ts +0 -2
  367. package/dist/types/serializer/base-serializer.d.ts +0 -15
  368. package/dist/types/serializer/comment/agent-comment-serializer.d.ts +0 -34
  369. package/dist/types/serializer/comment/agent-serializer.d.ts +0 -15
  370. package/dist/types/serializer/comment/comment-rule-serializer.d.ts +0 -68
  371. package/dist/types/serializer/comment/config-comment-serializer.d.ts +0 -25
  372. package/dist/types/serializer/comment/hint-comment-serializer.d.ts +0 -23
  373. package/dist/types/serializer/comment/hint-serializer.d.ts +0 -24
  374. package/dist/types/serializer/comment/metadata-comment-serializer.d.ts +0 -25
  375. package/dist/types/serializer/comment/pre-processor-comment-serializer.d.ts +0 -28
  376. package/dist/types/serializer/comment/simple-comment-serializer.d.ts +0 -24
  377. package/dist/types/serializer/cosmetic/body/abp-snippet-injection-body-serializer.d.ts +0 -25
  378. package/dist/types/serializer/cosmetic/body/adg-scriptlet-injection-body-serializer.d.ts +0 -25
  379. package/dist/types/serializer/cosmetic/body/scriptlet-body-serializer.d.ts +0 -17
  380. package/dist/types/serializer/cosmetic/body/ubo-scriptlet-injection-body-serializer.d.ts +0 -25
  381. package/dist/types/serializer/cosmetic/cosmetic-rule-serializer.d.ts +0 -19
  382. package/dist/types/serializer/cosmetic/css-injection-body-serializer.d.ts +0 -15
  383. package/dist/types/serializer/cosmetic/element-hiding-body-serializer.d.ts +0 -15
  384. package/dist/types/serializer/empty-rule-serializer.d.ts +0 -15
  385. package/dist/types/serializer/filterlist-serializer.d.ts +0 -15
  386. package/dist/types/serializer/index.d.ts +0 -1
  387. package/dist/types/serializer/invalid-rule-error-node-serializer.d.ts +0 -15
  388. package/dist/types/serializer/invalid-rule-serializer.d.ts +0 -15
  389. package/dist/types/serializer/misc/domain-list-serializer.d.ts +0 -21
  390. package/dist/types/serializer/misc/list-item-serializer.d.ts +0 -16
  391. package/dist/types/serializer/misc/list-items-serializer.d.ts +0 -15
  392. package/dist/types/serializer/misc/logical-expression-serializer.d.ts +0 -52
  393. package/dist/types/serializer/misc/modifier-list-serializer.d.ts +0 -20
  394. package/dist/types/serializer/misc/modifier-serializer.d.ts +0 -18
  395. package/dist/types/serializer/misc/parameter-list-serializer.d.ts +0 -17
  396. package/dist/types/serializer/misc/value-serializer.d.ts +0 -17
  397. package/dist/types/serializer/network/host-rule-serializer.d.ts +0 -30
  398. package/dist/types/serializer/network/hostname-list-serializer.d.ts +0 -15
  399. package/dist/types/serializer/network/network-rule-serializer.d.ts +0 -18
  400. package/dist/types/serializer/rule-serializer.d.ts +0 -17
  401. package/dist/types/utils/binary-schema-version.d.ts +0 -9
  402. package/dist/types/utils/byte-buffer.d.ts +0 -54
  403. package/dist/types/utils/input-byte-buffer.d.ts +0 -146
  404. package/dist/types/utils/is-chromium.d.ts +0 -7
  405. package/dist/types/utils/output-byte-buffer.d.ts +0 -132
  406. package/dist/types/utils/storage-interface.d.ts +0 -23
  407. package/dist/types/utils/text-decoder-polyfill.d.ts +0 -14
  408. package/dist/types/utils/text-encoder-polyfill.d.ts +0 -18
  409. package/dist/utils/binary-schema-version.js +0 -17
  410. package/dist/utils/byte-buffer.js +0 -91
  411. package/dist/utils/input-byte-buffer.js +0 -266
  412. package/dist/utils/is-chromium.js +0 -20
  413. package/dist/utils/output-byte-buffer.js +0 -231
  414. package/dist/utils/text-decoder-polyfill.js +0 -107
  415. package/dist/utils/text-encoder-polyfill.js +0 -78
  416. /package/dist/types/generator/cosmetic/{body → scriptlet-body}/abp-snippet-injection-body-generator.d.ts +0 -0
  417. /package/dist/types/generator/cosmetic/{body → scriptlet-body}/adg-scriptlet-injection-body-generator.d.ts +0 -0
  418. /package/dist/types/generator/cosmetic/{body → scriptlet-body}/ubo-scriptlet-injection-body-generator.d.ts +0 -0
  419. /package/dist/types/parser/cosmetic/{body → scriptlet-body}/abp-snippet-injection-body-parser.d.ts +0 -0
  420. /package/dist/types/parser/cosmetic/{body → scriptlet-body}/adg-scriptlet-injection-body-parser.d.ts +0 -0
  421. /package/dist/types/parser/cosmetic/{body → scriptlet-body}/ubo-scriptlet-injection-body-parser.d.ts +0 -0
@@ -1,25 +1,22 @@
1
1
  /*
2
- * AGTree v3.4.1 (build date: Fri, 28 Nov 2025 11:48:53 GMT)
3
- * (c) 2025 Adguard Software Ltd.
2
+ * AGTree v4.0.0 (build date: Wed, 21 Jan 2026 17:25:36 GMT)
3
+ * (c) 2026 Adguard Software Ltd.
4
4
  * Released under the MIT license
5
5
  * https://github.com/AdguardTeam/tsurlfilter/tree/master/packages/agtree#readme
6
6
  */
7
- import { getFormattedTokenName, TokenType } from '@adguard/css-tokenizer';
8
7
  import { sprintf } from 'sprintf-js';
9
8
  import { CosmeticRuleSeparator, CosmeticRuleType, RuleCategory } from '../../nodes/index.js';
10
9
  import { AdblockSyntax } from '../../utils/adblockers.js';
11
10
  import { RuleConversionError } from '../../errors/rule-conversion-error.js';
12
11
  import { RuleConverterBase } from '../base-interfaces/rule-converter-base.js';
13
- import { RegExpUtils } from '../../utils/regexp.js';
14
12
  import { createNodeConversionResult } from '../base-interfaces/conversion-result.js';
15
13
  import { cloneDomainListNode } from '../../ast-utils/clone.js';
16
- import { CssTokenStream } from '../../parser/css/css-token-stream.js';
17
- import { UBO_HTML_MASK, EQUALS, EMPTY, ESCAPE_CHARACTER, CSS_PSEUDO_MARKER, CSS_PSEUDO_OPEN, CSS_PSEUDO_CLOSE, OPEN_SQUARE_BRACKET, CLOSE_SQUARE_BRACKET } from '../../utils/constants.js';
18
- import { StringUtils, DOUBLE_QUOTE_MARKER } from '../../utils/string.js';
19
- import 'tldts';
20
- import { QuoteUtils } from '../../utils/quotes.js';
21
- import 'json5';
22
- import '../../parser/css/balancing.js';
14
+ import { EMPTY, EQUALS } from '../../utils/constants.js';
15
+ import { RegExpUtils } from '../../utils/regexp.js';
16
+ import { AdgHtmlFilteringBodyParser } from '../../parser/cosmetic/html-filtering-body/adg-html-filtering-body-parser.js';
17
+ import { UboHtmlFilteringBodyParser } from '../../parser/cosmetic/html-filtering-body/ubo-html-filtering-body-parser.js';
18
+ import { AdgHtmlFilteringBodyGenerator } from '../../generator/cosmetic/html-filtering-body/adg-html-filtering-body-generator.js';
19
+ import { UboHtmlFilteringBodyGenerator } from '../../generator/cosmetic/html-filtering-body/ubo-html-filtering-body-generator.js';
23
20
 
24
21
  /**
25
22
  * @file HTML filtering rule converter
@@ -35,152 +32,82 @@ import '../../parser/css/balancing.js';
35
32
  */
36
33
  const ADG_HTML_DEFAULT_MAX_LENGTH = 8192;
37
34
  const ADG_HTML_CONVERSION_MAX_LENGTH = ADG_HTML_DEFAULT_MAX_LENGTH * 32;
38
- const NOT_SPECIFIED = -1;
39
- const PseudoClasses = {
40
- Contains: 'contains',
35
+ /**
36
+ * Supported special pseudo-classes from uBlock.
37
+ *
38
+ * Note: If new pseudo-classes are added here, ensure to update
39
+ * the set and logic in the converter methods accordingly.
40
+ */
41
+ const UboPseudoClasses = {
41
42
  HasText: 'has-text',
42
43
  MinTextLength: 'min-text-length',
43
44
  };
44
45
  /**
45
- * Constructs a pseudo-class string with a specified value for use in CSS selectors.
46
+ * Supported special attribute selectors from AdGuard.
46
47
  *
47
- * @param pseudo - The pseudo-class name.
48
- * @param value - The value of the pseudo-class.
49
- * @returns pseudo-class string, including pseudo-class name, value and delimiters.
48
+ * Note: If new pseudo-classes are added here, ensure to update
49
+ * the set and logic in the converter methods accordingly.
50
50
  */
51
- const addPseudoClassWithValue = (pseudo, value) => {
52
- return `${CSS_PSEUDO_MARKER}${pseudo}${CSS_PSEUDO_OPEN}${value}${CSS_PSEUDO_CLOSE}`;
53
- };
54
- const AttributeSelectors = {
51
+ const AdgAttributeSelectors = {
55
52
  MaxLength: 'max-length',
56
53
  MinLength: 'min-length',
57
- TagContent: 'tag-content'};
58
- const SUPPORTED_UBO_PSEUDO_CLASSES = new Set([
59
- PseudoClasses.Contains,
60
- PseudoClasses.HasText,
61
- PseudoClasses.MinTextLength,
62
- ]);
63
- const ERROR_MESSAGES = {
64
- ABP_NOT_SUPPORTED: 'Invalid rule, ABP does not support HTML filtering rules',
65
- TAG_SHOULD_BE_FIRST_CHILD: "Unexpected token '%s' with value '%s', tag selector should be the first child",
66
- INVALID_ATTRIBUTE_NAME: "Attribute name should be an identifier, but got '%s' with value '%s'",
67
- // eslint-disable-next-line max-len
68
- INVALID_ATTRIBUTE_VALUE: `Expected '${getFormattedTokenName(TokenType.Ident)}' or '${getFormattedTokenName(TokenType.String)}' as attribute value, but got '%s' with value '%s`,
69
- VALUE_FOR_ATTR_SHOULD_BE_INT: "Value for '%s' attribute should be an integer, but got '%s'",
70
- INVALID_PSEUDO_CLASS: "Unsupported pseudo class '%s'",
71
- VALUE_FOR_PSEUDO_CLASS_SHOULD_BE_INT: "Value for '%s' pseudo class should be an integer, but got '%s'",
72
- // eslint-disable-next-line max-len
73
- REGEXP_NOT_SUPPORTED: "Cannot convert RegExp parameter '%s' from '%s' pseudo class, because converting RegExp patterns are not supported yet",
74
- ATTRIBUTE_SELECTOR_REQUIRES_VALUE: "Attribute selector '%s' requires a value",
75
- VALUE_SHOULD_BE_SPECIFIED: 'Value should be specified if operator is specified',
76
- VALUE_SHOULD_BE_POSITIVE: 'Value should be positive',
77
- UNEXPECTED_TOKEN_WITH_VALUE: "Unexpected token '%s' with value '%s'",
78
- FLAGS_NOT_SUPPORTED: 'Flags are not supported for attribute selectors',
54
+ TagContent: 'tag-content',
55
+ Wildcard: 'wildcard',
79
56
  };
80
57
  /**
81
- * Convert `""` to `\"` within strings, because it does not compatible with the standard CSS syntax.
58
+ * Supported special pseudo-classes from AdGuard.
82
59
  *
83
- * @param selector CSS selector string
84
- * @returns Escaped CSS selector
85
- * @note In the legacy syntax, `""` is used to escape double quotes, but it cannot be used in the standard CSS syntax,
86
- * so we use conversion functions to handle this.
87
- * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#tag-content}
60
+ * Note: If new pseudo-classes are added here, ensure to update
61
+ * the set and logic in the converter methods accordingly.
88
62
  */
89
- function escapeDoubleQuotes(selector) {
90
- let withinString = false;
91
- const buffer = [];
92
- for (let i = 0; i < selector.length; i += 1) {
93
- if (!withinString && selector[i] === DOUBLE_QUOTE_MARKER) {
94
- withinString = true;
95
- buffer.push(selector[i]);
96
- }
97
- else if (withinString && selector[i] === DOUBLE_QUOTE_MARKER && selector[i + 1] === DOUBLE_QUOTE_MARKER) {
98
- buffer.push(ESCAPE_CHARACTER);
99
- buffer.push(DOUBLE_QUOTE_MARKER);
100
- i += 1;
101
- }
102
- else if (withinString && selector[i] === DOUBLE_QUOTE_MARKER && selector[i + 1] !== DOUBLE_QUOTE_MARKER) {
103
- buffer.push(DOUBLE_QUOTE_MARKER);
104
- withinString = false;
105
- }
106
- else {
107
- buffer.push(selector[i]);
108
- }
109
- }
110
- return buffer.join(EMPTY);
111
- }
63
+ const AdgPseudoClasses = {
64
+ Contains: 'contains',
65
+ };
112
66
  /**
113
- * Safely parses length values from attribute selectors, like `"262144"` from `[max-length="262144"]`
114
- *
115
- * @param value The string value to parse
116
- * @param attrName The attribute name for error messages
117
- * @returns Parsed number
118
- * @throws A {@link RuleConversionError} if parsing fails
67
+ * Set of {@link UboPseudoClasses}.
119
68
  */
120
- function parseLengthValue(value, attrName) {
121
- const cleanValue = QuoteUtils.removeQuotes(value);
122
- const parsed = Number(cleanValue);
123
- if (Number.isNaN(parsed)) {
124
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.VALUE_FOR_ATTR_SHOULD_BE_INT, attrName, value));
125
- }
126
- if (parsed < 0) {
127
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.VALUE_SHOULD_BE_POSITIVE, attrName, value));
128
- }
129
- return parsed;
130
- }
69
+ const SUPPORTED_UBO_PSEUDO_CLASSES = new Set([
70
+ UboPseudoClasses.HasText,
71
+ UboPseudoClasses.MinTextLength,
72
+ ]);
131
73
  /**
132
- * Convert escaped double quotes `\"` to `""` within strings.
133
- *
134
- * @param selector CSS selector string
135
- * @returns Unescaped CSS selector
136
- * @note In the legacy syntax, `""` is used to escape double quotes, but it cannot be used in the standard CSS syntax,
137
- * so we use conversion functions to handle this.
138
- * @see {@link https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#tag-content}
74
+ * Set of {@link AdgAttributeSelectors}.
139
75
  */
140
- function unescapeDoubleQuotes(selector) {
141
- let withinString = false;
142
- const buffer = [];
143
- for (let i = 0; i < selector.length; i += 1) {
144
- if (selector[i] === DOUBLE_QUOTE_MARKER && selector[i - 1] !== ESCAPE_CHARACTER) {
145
- withinString = !withinString;
146
- buffer.push(selector[i]);
147
- }
148
- else if (withinString && selector[i] === ESCAPE_CHARACTER && selector[i + 1] === DOUBLE_QUOTE_MARKER) {
149
- buffer.push(DOUBLE_QUOTE_MARKER);
150
- }
151
- else {
152
- buffer.push(selector[i]);
153
- }
154
- }
155
- return buffer.join(EMPTY);
156
- }
76
+ const SUPPORTED_ADG_ATTRIBUTE_SELECTORS = new Set([
77
+ AdgAttributeSelectors.MaxLength,
78
+ AdgAttributeSelectors.MinLength,
79
+ AdgAttributeSelectors.TagContent,
80
+ AdgAttributeSelectors.Wildcard,
81
+ ]);
157
82
  /**
158
- * Helper function to render an attribute selector
159
- *
160
- * @param attr Attribute name
161
- * @param op Operator (optional)
162
- * @param value Attribute value (optional)
163
- * @param flags Attribute flags (optional)
164
- * @returns Rendered attribute selector string
83
+ * Set of {@link AdgPseudoClasses}.
165
84
  */
166
- function renderAttrSelector(attr, op, value, flags) {
167
- const result = [];
168
- result.push(OPEN_SQUARE_BRACKET);
169
- result.push(attr);
170
- {
171
- if (value === undefined) {
172
- throw new Error(ERROR_MESSAGES.VALUE_SHOULD_BE_SPECIFIED);
173
- }
174
- result.push(op);
175
- }
176
- if (value !== undefined) {
177
- result.push(DOUBLE_QUOTE_MARKER);
178
- result.push(value);
179
- result.push(DOUBLE_QUOTE_MARKER);
180
- }
181
- result.push(CLOSE_SQUARE_BRACKET);
182
- return result.join(EMPTY);
183
- }
85
+ const SUPPORTED_ADG_PSEUDO_CLASSES = new Set([
86
+ AdgPseudoClasses.Contains,
87
+ ]);
88
+ /**
89
+ * Error messages used in HTML filtering rule conversion.
90
+ */
91
+ /* eslint-disable max-len */
92
+ const ERROR_MESSAGES = {
93
+ ABP_NOT_SUPPORTED: 'Invalid rule, ABP does not support HTML filtering rules',
94
+ INVALID_RULE: 'Invalid HTML filtering rule: %s',
95
+ MIXED_SYNTAX_ADG_UBO: 'Mixed AdGuard and uBlock syntax',
96
+ EMPTY_SELECTOR_LIST: 'Selector list of HTML filtering rule must not be empty',
97
+ EMPTY_COMPLEX_SELECTOR: 'Complex selector of selector list must not be empty',
98
+ INVALID_SELECTOR_COMBINATOR: "Invalid selector combinator '%s' used between selectors",
99
+ UNKNOWN_SELECTOR_TYPE: "Unknown selector type '%s' found during conversion",
100
+ SPECIAL_ATTRIBUTE_SELECTOR_OPERATOR_INVALID: "Special attribute selector '%s' has invalid operator '%s'",
101
+ SPECIAL_ATTRIBUTE_SELECTOR_FLAG_NOT_SUPPORTED: "Special attribute selector '%s' does not support flags",
102
+ SPECIAL_ATTRIBUTE_SELECTOR_VALUE_REQUIRED: "Special attribute selector '%s' requires a value",
103
+ SPECIAL_ATTRIBUTE_SELECTOR_VALUE_INT: "Value of special attribute selector '%s' must be an integer, got '%s'",
104
+ SPECIAL_ATTRIBUTE_SELECTOR_VALUE_POSITIVE: "Value of special attribute selector '%s' must be a positive integer, got '%s'",
105
+ SPECIAL_ATTRIBUTE_SELECTOR_NOT_SUPPORTED: "Special attribute selector '%s' is not supported in conversion",
106
+ SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_REQUIRED: "Special pseudo-class selector '%s' requires an argument",
107
+ SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_INT: "Argument of special pseudo-class selector '%s' must be an integer, got '%s'",
108
+ SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_POSITIVE: "Argument of special pseudo-class selector '%s' must be a positive integer, got '%s'",
109
+ SPECIAL_PSEUDO_CLASS_SELECTOR_NOT_SUPPORTED: "Special pseudo-class selector '%s' is not supported in conversion",
110
+ };
184
111
  /**
185
112
  * HTML filtering rule converter class
186
113
  *
@@ -188,344 +115,559 @@ function renderAttrSelector(attr, op, value, flags) {
188
115
  */
189
116
  class HtmlRuleConverter extends RuleConverterBase {
190
117
  /**
191
- * Converts a HTML rule to AdGuard syntax, if possible. Also can be used to convert
192
- * AdGuard rules to AdGuard syntax to validate them.
118
+ * Converts a HTML rule to AdGuard syntax, if possible.
119
+ * Also can be used to convert AdGuard rules to AdGuard syntax to validate them.
193
120
  *
194
- * _Note:_ uBlock Origin supports multiple selectors within a single rule, but AdGuard doesn't,
195
- * so the following rule
196
- * ```
197
- * example.com##^div[attr1="value1"][attr2="value2"], script:has-text(value)
198
- * ```
199
- * will be converted to multiple AdGuard rules:
200
- * ```
201
- * example.com$$div[attr1="value1"][attr2="value2"][max-length="262144"]
202
- * example.com$$script[tag-content="value"][max-length="262144"]
203
- * ```
121
+ * @param rule Rule node to convert.
204
122
  *
205
- * @param rule Rule node to convert
206
123
  * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
207
124
  * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
208
- * If the rule was not converted, the result array will contain the original node with the same object reference
209
- * @throws If the rule is invalid or cannot be converted
125
+ * If the rule was not converted, the result array will contain the original node with the same object reference.
126
+ *
127
+ * @throws If the rule is invalid or cannot be converted.
210
128
  */
211
129
  static convertToAdg(rule) {
212
- // Ignore AdGuard rules
130
+ let parser;
131
+ let onSpecialAttributeSelector;
132
+ let onSpecialPseudoClassSelector;
133
+ let isConverted = false;
213
134
  if (rule.syntax === AdblockSyntax.Adg) {
135
+ parser = AdgHtmlFilteringBodyParser;
136
+ onSpecialAttributeSelector = (name, value) => {
137
+ /**
138
+ * Mark rule as converted in ADG -> ADG conversion only if
139
+ * special attribute selectors are present in the rule body,
140
+ * because they are deprecated and will be removed soon,
141
+ * so we convert them to pseudo-class selectors
142
+ */
143
+ isConverted = true;
144
+ return HtmlRuleConverter.convertSpecialAttributeSelectorAdgToAdg(name, value);
145
+ };
146
+ onSpecialPseudoClassSelector = HtmlRuleConverter.convertSpecialPseudoClassSelectorAdgToAdg;
147
+ }
148
+ else if (rule.syntax === AdblockSyntax.Ubo) {
149
+ /**
150
+ * Always mark rule as converted in UBO -> ADG conversion.
151
+ */
152
+ isConverted = true;
153
+ parser = UboHtmlFilteringBodyParser;
154
+ onSpecialAttributeSelector = HtmlRuleConverter.convertSpecialAttributeSelectorUboToAdg;
155
+ onSpecialPseudoClassSelector = HtmlRuleConverter.convertSpecialPseudoClassSelectorUboToAdg;
156
+ }
157
+ else {
158
+ throw new RuleConversionError(ERROR_MESSAGES.ABP_NOT_SUPPORTED);
159
+ }
160
+ // Convert body
161
+ const convertedBody = HtmlRuleConverter.convertBody(rule.body, parser, AdgHtmlFilteringBodyGenerator, onSpecialAttributeSelector, onSpecialPseudoClassSelector);
162
+ if (!isConverted) {
163
+ return createNodeConversionResult([rule], false);
164
+ }
165
+ return createNodeConversionResult([{
166
+ category: RuleCategory.Cosmetic,
167
+ type: CosmeticRuleType.HtmlFilteringRule,
168
+ syntax: AdblockSyntax.Adg,
169
+ exception: rule.exception,
170
+ domains: cloneDomainListNode(rule.domains),
171
+ // Convert the separator based on the exception status
172
+ separator: {
173
+ type: 'Value',
174
+ value: rule.exception
175
+ ? CosmeticRuleSeparator.AdgHtmlFilteringException
176
+ : CosmeticRuleSeparator.AdgHtmlFiltering,
177
+ },
178
+ body: convertedBody,
179
+ }], true);
180
+ }
181
+ /**
182
+ * Converts a HTML rule to uBlock syntax, if possible.
183
+ * Also can be used to convert uBlock rules to uBlock syntax to validate them.
184
+ *
185
+ * @param rule Rule node to convert.
186
+ *
187
+ * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
188
+ * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
189
+ * If the rule was not converted, the result array will contain the original node with the same object reference.
190
+ *
191
+ * @throws Error if the rule is invalid or cannot be converted.
192
+ */
193
+ static convertToUbo(rule) {
194
+ // Ignore uBlock rules
195
+ if (rule.syntax === AdblockSyntax.Ubo) {
214
196
  return createNodeConversionResult([rule], false);
215
197
  }
216
198
  if (rule.syntax === AdblockSyntax.Abp) {
217
199
  throw new RuleConversionError(ERROR_MESSAGES.ABP_NOT_SUPPORTED);
218
200
  }
219
- const source = escapeDoubleQuotes(rule.body.value);
220
- const stream = new CssTokenStream(source);
221
- const convertedSelector = [];
222
- const convertedSelectorList = [];
223
- let minLen = NOT_SPECIFIED;
224
- let maxLen = NOT_SPECIFIED;
225
- // Skip leading whitespace
226
- stream.skipWhitespace();
227
- // Skip ^
228
- stream.expect(TokenType.Delim, { value: UBO_HTML_MASK });
229
- stream.advance();
230
- while (!stream.isEof()) {
231
- const token = stream.getOrFail();
232
- if (token.type === TokenType.Ident) {
233
- // Tag selector should be the first child, if present, but whitespace is allowed before it
234
- if (convertedSelector.length !== 0 && stream.lookbehindForNonWs() !== undefined) {
235
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.TAG_SHOULD_BE_FIRST_CHILD, getFormattedTokenName(token.type), source.slice(token.start, token.end)));
236
- }
237
- convertedSelector.push(source.slice(token.start, token.end));
238
- stream.advance();
201
+ // Convert body
202
+ const convertedBody = HtmlRuleConverter.convertBody(rule.body, AdgHtmlFilteringBodyParser, UboHtmlFilteringBodyGenerator, HtmlRuleConverter.convertSpecialAttributeSelectorAdgToUbo, HtmlRuleConverter.convertSpecialPseudoClassSelectorAdgToUbo);
203
+ return createNodeConversionResult([{
204
+ category: RuleCategory.Cosmetic,
205
+ type: CosmeticRuleType.HtmlFilteringRule,
206
+ syntax: AdblockSyntax.Ubo,
207
+ exception: rule.exception,
208
+ domains: cloneDomainListNode(rule.domains),
209
+ separator: {
210
+ type: 'Value',
211
+ value: rule.exception
212
+ ? CosmeticRuleSeparator.ElementHidingException
213
+ : CosmeticRuleSeparator.ElementHiding,
214
+ },
215
+ body: convertedBody,
216
+ }], true);
217
+ }
218
+ /**
219
+ * Handles special attribute selectors during AdGuard to AdGuard conversion:
220
+ * - `[tag-content="content"]` -> `:contains(content)`
221
+ * direct conversion, no changes to value
222
+ * - `[wildcard="*content*"]` -> `:contains(/*.content*./s)`
223
+ * convert search pattern to regular expression
224
+ * - `[min-length="min"]` -> `:contains(/^(?=.{min,}$).*\/s)`
225
+ * converts to a length-matching regular expression
226
+ * - `[max-length="max"]` -> `:contains(/^(?=.{0,max}$).*\/s)`
227
+ * converts to a length-matching regular expression
228
+ *
229
+ * Note: This attribute selector to pseudo-class selector conversion
230
+ * is needed because AdGuard special attribute selectors are going
231
+ * to be deprecated and removed soon.
232
+ *
233
+ * @param name Name of the special attribute selector.
234
+ * @param value Value of the special attribute selector.
235
+ *
236
+ * @returns A {@link SimpleSelector} to add to the current complex selector.
237
+ */
238
+ static convertSpecialAttributeSelectorAdgToAdg(name, value) {
239
+ switch (name) {
240
+ // `[tag-content="content"]` -> `:contains(content)`
241
+ // direct conversion, no changes to value
242
+ case AdgAttributeSelectors.TagContent: {
243
+ return HtmlRuleConverter.getPseudoClassSelectorNode(AdgPseudoClasses.Contains, value);
239
244
  }
240
- else if (token.type === TokenType.OpenSquareBracket) {
241
- // Attribute selectors: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax
242
- const { start } = token;
243
- let tempToken;
244
- // Advance opening square bracket
245
- stream.advance();
246
- // Skip optional whitespace after the opening square bracket
247
- stream.skipWhitespace();
248
- // Parse attribute name
249
- tempToken = stream.getOrFail();
250
- if (tempToken.type !== TokenType.Ident) {
251
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTE_NAME, getFormattedTokenName(tempToken.type), source.slice(tempToken.start, tempToken.end)));
252
- }
253
- const attr = source.slice(tempToken.start, tempToken.end);
254
- stream.advance();
255
- // Skip optional whitespace after the attribute name
256
- stream.skipWhitespace();
257
- // Maybe attribute selector ends here, because value is not required, like in '[disabled]'
258
- tempToken = stream.getOrFail();
259
- // So check if the next non whitespace token is a closing square bracket
260
- if (tempToken.type === TokenType.CloseSquareBracket) {
261
- const { end } = tempToken;
262
- stream.advance();
263
- // Special case for min-length and max-length attributes
264
- if (attr === AttributeSelectors.MinLength || attr === AttributeSelectors.MaxLength) {
265
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.ATTRIBUTE_SELECTOR_REQUIRES_VALUE, attr));
266
- }
267
- convertedSelector.push(source.slice(start, end));
268
- continue;
269
- }
270
- // Next token should be a valid attribute selector operator
271
- // Only '=' operator is supported
272
- stream.expect(TokenType.Delim, { value: EQUALS });
273
- // Advance the operator
274
- stream.advance();
275
- // Skip optional whitespace after the operator
276
- stream.skipWhitespace();
277
- // Parse attribute value
278
- tempToken = stream.getOrFail();
279
- // According to the spec, attribute value should be an identifier or a string
280
- if (tempToken.type !== TokenType.Ident && tempToken.type !== TokenType.String) {
281
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), source.slice(tempToken.start, tempToken.end)));
282
- }
283
- const value = source.slice(tempToken.start, tempToken.end);
284
- // Advance the attribute value
285
- stream.advance();
286
- // Skip optional whitespace after the attribute value
287
- stream.skipWhitespace();
288
- // Attribute selector may have flags - but AdGuard HTML filtering does not support them
289
- tempToken = stream.getOrFail();
290
- if (tempToken.type === TokenType.Ident) {
291
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.FLAGS_NOT_SUPPORTED));
292
- }
293
- // Next token should be a closing square bracket
294
- stream.expect(TokenType.CloseSquareBracket);
295
- const { end } = stream.getOrFail();
296
- stream.advance();
297
- if (attr === AttributeSelectors.MinLength) {
298
- // Min length attribute
299
- const parsed = parseInt(value, 10);
300
- if (Number.isNaN(parsed)) {
301
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.VALUE_FOR_ATTR_SHOULD_BE_INT, attr, value));
302
- }
303
- minLen = parsed;
304
- }
305
- else if (attr === AttributeSelectors.MaxLength) {
306
- // Max length attribute
307
- const parsed = parseInt(value, 10);
308
- if (Number.isNaN(parsed)) {
309
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.VALUE_FOR_ATTR_SHOULD_BE_INT, attr, value));
310
- }
311
- maxLen = parsed;
245
+ // `[wildcard="*content*"] -> `:contains(/*.content*./s)`
246
+ // convert search pattern to regular expression
247
+ case AdgAttributeSelectors.Wildcard: {
248
+ return HtmlRuleConverter.getPseudoClassSelectorNode(AdgPseudoClasses.Contains, RegExpUtils.globToRegExp(value));
249
+ }
250
+ // `[min-length="min"]` -> `:contains(/^(?=.{min,}$).*\/s)`
251
+ // `[max-length="max"]` -> `:contains(/^(?=.{0,max}$).*\/s)`
252
+ // converts to a length-matching regular expression
253
+ case AdgAttributeSelectors.MinLength:
254
+ case AdgAttributeSelectors.MaxLength: {
255
+ // Validate length value
256
+ HtmlRuleConverter.assertValidLengthValue(name, value, ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_VALUE_INT, ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_VALUE_POSITIVE);
257
+ // It's safe to cast to number here after validation
258
+ const length = Number(value);
259
+ let min = null;
260
+ let max = null;
261
+ if (name === AdgAttributeSelectors.MinLength) {
262
+ min = length;
312
263
  }
313
264
  else {
314
- convertedSelector.push(source.slice(start, end));
265
+ max = length;
315
266
  }
267
+ return HtmlRuleConverter.getPseudoClassSelectorNode(AdgPseudoClasses.Contains, RegExpUtils.getLengthRegexp(min, max));
316
268
  }
317
- else if (token.type === TokenType.Colon) {
318
- let tempToken;
319
- // Pseudo classes: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes#syntax
320
- stream.advance();
321
- // Next token should be a pseudo class name
322
- stream.expect(TokenType.Function);
323
- tempToken = stream.getOrFail();
324
- const fn = source.slice(tempToken.start, tempToken.end - 1); // do not include '('
325
- // Pseudo class should be supported
326
- if (!SUPPORTED_UBO_PSEUDO_CLASSES.has(fn)) {
327
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_PSEUDO_CLASS, fn));
328
- }
329
- const paramStart = tempToken.end;
330
- // Find the closing paren
331
- stream.skipUntilBalanced();
332
- tempToken = stream.getOrFail();
333
- const paramEnd = tempToken.end;
334
- // Get the parameter
335
- const param = source.slice(paramStart, paramEnd - 1);
336
- if (fn === PseudoClasses.MinTextLength) {
337
- // Min text length pseudo class
338
- // Parameter should be parsed as an integer
339
- const parsed = parseInt(param, 10);
340
- if (Number.isNaN(parsed)) {
341
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.VALUE_FOR_PSEUDO_CLASS_SHOULD_BE_INT, fn, param));
342
- }
343
- minLen = parsed;
344
- }
345
- else if (fn === PseudoClasses.Contains || fn === PseudoClasses.HasText) {
346
- // Contains and has-text pseudo classes
347
- // Check if the argument is a RegExp
348
- if (RegExpUtils.isRegexPattern(param)) {
349
- // TODO: Add some support for RegExp patterns later
350
- // Need to find a way to convert some RegExp patterns to glob patterns
351
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.REGEXP_NOT_SUPPORTED, param, fn));
352
- }
353
- // Escape unescaped double quotes in the parameter
354
- const paramEscaped = StringUtils.escapeCharacter(param, DOUBLE_QUOTE_MARKER);
355
- convertedSelector.push(renderAttrSelector(AttributeSelectors.TagContent, EQUALS, paramEscaped));
356
- }
357
- stream.advance();
269
+ // This line is unreachable due to exhausted cases, but we keep it to satisfy TS
270
+ default: {
271
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_NOT_SUPPORTED, name));
358
272
  }
359
- else if (token.type === TokenType.Comma && token.balance === 0) {
360
- if (minLen !== NOT_SPECIFIED) {
361
- convertedSelector.push(renderAttrSelector(AttributeSelectors.MinLength, EQUALS, minLen.toString()));
362
- }
363
- convertedSelector.push(renderAttrSelector(AttributeSelectors.MaxLength, EQUALS, maxLen !== NOT_SPECIFIED ? maxLen.toString() : ADG_HTML_CONVERSION_MAX_LENGTH.toString()));
364
- convertedSelectorList.push(convertedSelector.join(EMPTY));
365
- convertedSelector.length = 0;
366
- stream.advance();
273
+ }
274
+ }
275
+ /**
276
+ * Since special pseudo-class selectors do not need conversion
277
+ * in AdGuard to AdGuard conversion, we simply return `true` to keep them as-is.
278
+ *
279
+ * @param name Name of the special pseudo-class selector.
280
+ *
281
+ * @returns `true` to keep the special pseudo-class selector as-is.
282
+ *
283
+ * @throws Rule conversion error for mixed syntax.
284
+ */
285
+ static convertSpecialPseudoClassSelectorAdgToAdg(name) {
286
+ if (SUPPORTED_UBO_PSEUDO_CLASSES.has(name)) {
287
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, ERROR_MESSAGES.MIXED_SYNTAX_ADG_UBO));
288
+ }
289
+ return true;
290
+ }
291
+ /**
292
+ * Since special attribute selectors only AdGuard-specific,
293
+ * we should never encounter them in uBlock rules.
294
+ *
295
+ * @throws Rule conversion error for mixed syntax.
296
+ */
297
+ static convertSpecialAttributeSelectorUboToAdg() {
298
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, ERROR_MESSAGES.MIXED_SYNTAX_ADG_UBO));
299
+ }
300
+ /**
301
+ * Handles special pseudo-class selectors during uBlock to AdGuard conversion:
302
+ * - `:has-text(text)` -> `:contains(text)`
303
+ * direct conversion, no changes to argument
304
+ * - `:min-text-length(min)` -> `:contains(/^(?=.{min,MAX_CONVERSION_DEFAULT}$).*\/s)`
305
+ * converts to a length-matching regular expression
306
+ *
307
+ * @param name Name of the special pseudo-class selector.
308
+ * @param argument Argument of the special pseudo-class selector.
309
+ *
310
+ * @returns A {@link SimpleSelector} to add to the current complex selector.
311
+ *
312
+ * @throws If AdGuard-specific pseudo-class selector is found in uBlock rule.
313
+ */
314
+ static convertSpecialPseudoClassSelectorUboToAdg(name, argument) {
315
+ switch (name) {
316
+ // `:has-text(text)` -> `:contains(text)`
317
+ // direct conversion, no changes to argument
318
+ case UboPseudoClasses.HasText: {
319
+ return HtmlRuleConverter.getPseudoClassSelectorNode(AdgPseudoClasses.Contains, argument);
320
+ }
321
+ // `:min-text-length(min)` -> `:contains(/^(?=.{min,MAX_CONVERSION_DEFAULT}$).*\/s)`
322
+ // converts to a length-matching regular expression
323
+ case UboPseudoClasses.MinTextLength: {
324
+ // Validate length value
325
+ HtmlRuleConverter.assertValidLengthValue(name, argument, ERROR_MESSAGES.SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_INT, ERROR_MESSAGES.SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_POSITIVE);
326
+ // It's safe to cast to number here after validation
327
+ const minLength = Number(argument);
328
+ return HtmlRuleConverter.getPseudoClassSelectorNode(AdgPseudoClasses.Contains, RegExpUtils.getLengthRegexp(minLength, ADG_HTML_CONVERSION_MAX_LENGTH));
367
329
  }
368
- else if (token.type === TokenType.Whitespace) {
369
- stream.advance();
330
+ // Throw an error if the AdGuard-specific pseudo-class selector found in uBlock rule
331
+ case AdgPseudoClasses.Contains: {
332
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, ERROR_MESSAGES.MIXED_SYNTAX_ADG_UBO));
370
333
  }
371
- else {
372
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.UNEXPECTED_TOKEN_WITH_VALUE, getFormattedTokenName(token.type), source.slice(token.start, token.end)));
334
+ // This line is unreachable due to exhausted cases, but we keep it to satisfy TS
335
+ default: {
336
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_PSEUDO_CLASS_SELECTOR_NOT_SUPPORTED, name));
373
337
  }
374
338
  }
375
- if (convertedSelector.length !== 0) {
376
- if (minLen !== NOT_SPECIFIED) {
377
- convertedSelector.push(renderAttrSelector(AttributeSelectors.MinLength, EQUALS, minLen.toString()));
339
+ }
340
+ /**
341
+ * Handles special attribute selectors during AdGuard to uBlock conversion:
342
+ * - `[tag-content="content"]` -> `:has-text(content)`
343
+ * direct conversion, no changes to value
344
+ * - `[wildcard="*content*"]` -> `:has-text(/*.content*./s)`
345
+ * convert search pattern to regular expression
346
+ * - `[min-length="min"]` -> `:min-text-length(min)`
347
+ * direct conversion, no changes to value
348
+ * - `[max-length]` is skipped
349
+ *
350
+ * @param name Name of the special attribute selector.
351
+ * @param value Value of the special attribute selector.
352
+ *
353
+ * @returns A {@link SimpleSelector} to add to the current complex selector, or `false` to skip it.
354
+ */
355
+ static convertSpecialAttributeSelectorAdgToUbo(name, value) {
356
+ switch (name) {
357
+ // `[tag-content="content"]` -> `:has-text(content)`
358
+ // direct conversion, no changes to value
359
+ case AdgAttributeSelectors.TagContent: {
360
+ return HtmlRuleConverter.getPseudoClassSelectorNode(UboPseudoClasses.HasText, value);
361
+ }
362
+ // `[wildcard="*content*"] -> `:has-text(/*.content*./s)`
363
+ // convert search pattern to regular expression
364
+ case AdgAttributeSelectors.Wildcard: {
365
+ return HtmlRuleConverter.getPseudoClassSelectorNode(UboPseudoClasses.HasText, RegExpUtils.globToRegExp(value));
366
+ }
367
+ // `[min-length="min"]` -> `:min-text-length(min)`
368
+ // direct conversion, no changes to value
369
+ case AdgAttributeSelectors.MinLength: {
370
+ // Validate length value
371
+ HtmlRuleConverter.assertValidLengthValue(name, value, ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_VALUE_INT, ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_VALUE_POSITIVE);
372
+ return HtmlRuleConverter.getPseudoClassSelectorNode(UboPseudoClasses.MinTextLength, value);
373
+ }
374
+ // `[max-length]` is skipped
375
+ case AdgAttributeSelectors.MaxLength: {
376
+ return false;
377
+ }
378
+ // This line is unreachable due to exhausted cases, but we keep it to satisfy TS
379
+ default: {
380
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_NOT_SUPPORTED, name));
378
381
  }
379
- convertedSelector.push(renderAttrSelector(AttributeSelectors.MaxLength, EQUALS, maxLen !== NOT_SPECIFIED ? maxLen.toString() : ADG_HTML_CONVERSION_MAX_LENGTH.toString()));
380
- convertedSelectorList.push(convertedSelector.join(EMPTY));
381
382
  }
382
- return createNodeConversionResult(
383
- // Since AdGuard HTML filtering rules do not support multiple selectors, we need to split each selector
384
- // into a separate rule node.
385
- convertedSelectorList.map((selector) => ({
386
- category: RuleCategory.Cosmetic,
387
- type: CosmeticRuleType.HtmlFilteringRule,
388
- syntax: AdblockSyntax.Adg,
389
- exception: rule.exception,
390
- domains: cloneDomainListNode(rule.domains),
391
- // Convert the separator based on the exception status
392
- separator: {
393
- type: 'Value',
394
- value: rule.exception
395
- ? CosmeticRuleSeparator.AdgHtmlFilteringException
396
- : CosmeticRuleSeparator.AdgHtmlFiltering,
397
- },
398
- body: {
399
- type: 'Value',
400
- value: unescapeDoubleQuotes(selector),
401
- },
402
- })), true);
403
383
  }
404
384
  /**
405
- * Converts a HTML rule to uBlock Origin syntax, if possible.
385
+ * Handles special pseudo-class selectors during AdGuard to uBlock conversion:
386
+ * - `:contains(text)` -> `:has-text(text)`
387
+ * direct conversion, no changes to argument
406
388
  *
407
- * @note AdGuard rules are often more specific than uBlock Origin rules, so some information
408
- * may be lost in conversion. AdGuard's `[max-length]` and `[tag-content]` attributes will be converted to
409
- * uBlock's `:min-text-length()` and `:has-text()` pseudo-classes when possible.
389
+ * @param name Name of the special pseudo-class selector.
390
+ * @param argument Argument of the special pseudo-class selector.
410
391
  *
411
- * @param rule Rule node to convert
412
- * @returns An object which follows the {@link NodeConversionResult} interface. Its `result` property contains
413
- * the array of converted rule nodes, and its `isConverted` flag indicates whether the original rule was converted.
414
- * If the rule was not converted, the result array will contain the original node with the same object reference
415
- * @throws Error if the rule is invalid or cannot be converted
392
+ * @returns A {@link SimpleSelector} to add to the current complex selector.
393
+ *
394
+ * @throws If uBlock-specific pseudo-class selector is found in AdGuard rule.
416
395
  */
417
- static convertToUbo(rule) {
418
- // Ignore uBlock Origin rules
419
- if (rule.syntax === AdblockSyntax.Ubo) {
420
- return createNodeConversionResult([rule], false);
396
+ static convertSpecialPseudoClassSelectorAdgToUbo(name, argument) {
397
+ switch (name) {
398
+ // `:contains(text)` -> `:has-text(text)`
399
+ // direct conversion, no changes to argument
400
+ case AdgPseudoClasses.Contains: {
401
+ return HtmlRuleConverter.getPseudoClassSelectorNode(UboPseudoClasses.HasText, argument);
402
+ }
403
+ // Throw an error if the uBlock-specific pseudo-class selector found in AdGuard rule
404
+ case UboPseudoClasses.HasText:
405
+ case UboPseudoClasses.MinTextLength: {
406
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, ERROR_MESSAGES.MIXED_SYNTAX_ADG_UBO));
407
+ }
408
+ // This line is unreachable due to exhausted cases, but we keep it to satisfy TS
409
+ default: {
410
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_PSEUDO_CLASS_SELECTOR_NOT_SUPPORTED, name));
411
+ }
421
412
  }
422
- if (rule.syntax === AdblockSyntax.Abp) {
423
- throw new RuleConversionError(ERROR_MESSAGES.ABP_NOT_SUPPORTED);
413
+ }
414
+ /**
415
+ * Converts a HTML filtering rule body by handling special simple selectors via callbacks.
416
+ * Special simple selectors are skipped in the converted selector list and should be handled from callee.
417
+ *
418
+ * @param body HTML filtering rule body to convert.
419
+ * @param parser HTML filtering rule body parser used for parsing raw value bodies.
420
+ * @param generator HTML filtering rule body generator used for generating raw value bodies.
421
+ * @param onSpecialAttributeSelector Callback invoked when a special attribute selector is found.
422
+ * @param onSpecialPseudoClassSelector Callback invoked when a special pseudo-class selector is found.
423
+ *
424
+ * @returns Converted selector list without special simple selectors.
425
+ */
426
+ static convertBody(body, parser, generator, onSpecialAttributeSelector, onSpecialPseudoClassSelector) {
427
+ // Handle case when body is raw value string.
428
+ // If so, parse it first as we need to work with AST nodes.
429
+ let processedBody;
430
+ if (body.type === 'Value') {
431
+ processedBody = parser.parse(body.value, {
432
+ isLocIncluded: false,
433
+ parseHtmlFilteringRuleBodies: true,
434
+ });
424
435
  }
425
- const source = escapeDoubleQuotes(rule.body.value);
426
- const stream = new CssTokenStream(source);
427
- const convertedSelector = [];
428
- let minTextLength;
429
- // Skip leading whitespace
430
- stream.skipWhitespace();
431
- while (!stream.isEof()) {
432
- const token = stream.getOrFail();
433
- if (token.type === TokenType.Ident) {
434
- convertedSelector.push(source.slice(token.start, token.end));
435
- stream.advance();
436
- }
437
- else if (token.type === TokenType.OpenSquareBracket) {
438
- // Attribute selectors
439
- const { start } = token;
440
- let tempToken;
441
- // Advance opening square bracket
442
- stream.advance();
443
- // Skip optional whitespace
444
- stream.skipWhitespace();
445
- // Parse attribute name
446
- tempToken = stream.getOrFail();
447
- if (tempToken.type !== TokenType.Ident) {
448
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTE_NAME, getFormattedTokenName(tempToken.type), source.slice(tempToken.start, tempToken.end)));
449
- }
450
- const attr = source.slice(tempToken.start, tempToken.end);
451
- stream.advance();
452
- // Skip optional whitespace
453
- stream.skipWhitespace();
454
- // Check if this is a standalone attribute (like [disabled])
455
- tempToken = stream.getOrFail();
456
- if (tempToken.type === TokenType.CloseSquareBracket) {
457
- const { end } = tempToken;
458
- stream.advance();
459
- convertedSelector.push(source.slice(start, end));
460
- continue;
461
- }
462
- // Expect equals operator
463
- stream.expect(TokenType.Delim, { value: EQUALS });
464
- stream.advance();
465
- // Skip optional whitespace
466
- stream.skipWhitespace();
467
- // Parse attribute value
468
- tempToken = stream.getOrFail();
469
- if (tempToken.type !== TokenType.Ident && tempToken.type !== TokenType.String) {
470
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTE_VALUE, getFormattedTokenName(tempToken.type), source.slice(tempToken.start, tempToken.end)));
471
- }
472
- const value = source.slice(tempToken.start, tempToken.end);
473
- stream.advance();
474
- // Skip optional whitespace
475
- stream.skipWhitespace();
476
- // Check for closing bracket
477
- tempToken = stream.getOrFail();
478
- if (tempToken.type !== TokenType.CloseSquareBracket) {
479
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.FLAGS_NOT_SUPPORTED));
480
- }
481
- const { end } = stream.getOrFail();
482
- stream.advance();
483
- // Handle special attributes with improved number parsing
484
- if (attr === AttributeSelectors.MinLength || attr === AttributeSelectors.MaxLength) {
485
- const parsedValue = parseLengthValue(value, attr);
486
- if (attr === AttributeSelectors.MinLength) {
487
- minTextLength = parsedValue;
436
+ else {
437
+ processedBody = body;
438
+ }
439
+ const { children: complexSelectors } = processedBody.selectorList;
440
+ // Selector list node must not be empty
441
+ HtmlRuleConverter.assertNotEmpty(complexSelectors, ERROR_MESSAGES.EMPTY_SELECTOR_LIST);
442
+ // Convert each complex selector
443
+ const convertedComplexSelectors = [];
444
+ for (let i = 0; i < complexSelectors.length; i += 1) {
445
+ const { children: selectors } = complexSelectors[i];
446
+ // Complex selector node must not be empty
447
+ HtmlRuleConverter.assertNotEmpty(selectors, ERROR_MESSAGES.EMPTY_COMPLEX_SELECTOR);
448
+ // Convert each selector
449
+ const convertedSelectors = [];
450
+ for (let j = 0; j < selectors.length; j += 1) {
451
+ const selector = selectors[j];
452
+ switch (selector.type) {
453
+ case 'SelectorCombinator': {
454
+ // Throw if selector combinator used incorrectly
455
+ if (
456
+ // If first selector in the complex selector (`> div`)
457
+ j === 0
458
+ // If the previous selector is also a combinator (`div > + span`)
459
+ || j === selectors.length - 1
460
+ // If the last selector in the complex selector (`div +`)
461
+ || (j > 0 && selectors[j - 1].type === 'SelectorCombinator')) {
462
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, sprintf(ERROR_MESSAGES.INVALID_SELECTOR_COMBINATOR, selector.value)));
463
+ }
464
+ break;
465
+ }
466
+ case 'AttributeSelector': {
467
+ // Not a special attribute selector - clone as-is after the switch
468
+ if (!SUPPORTED_ADG_ATTRIBUTE_SELECTORS.has(selector.name.value)) {
469
+ break;
470
+ }
471
+ // Throw an error if value is missing
472
+ if (!('value' in selector) || selector.value.value === EMPTY) {
473
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_VALUE_REQUIRED, selector.name.value));
474
+ }
475
+ // Throw an error if operator is not '='
476
+ if (selector.operator.value !== EQUALS) {
477
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_OPERATOR_INVALID, selector.name.value, selector.operator.value));
478
+ }
479
+ // Throw an error if flag is specified
480
+ if (selector.flag) {
481
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_ATTRIBUTE_SELECTOR_FLAG_NOT_SUPPORTED, selector.name.value));
482
+ }
483
+ const name = selector.name.value;
484
+ const { value } = selector.value;
485
+ // Invoke callback and:
486
+ // - add returned simple selector if it's not boolean
487
+ // - skip adding if returned value is false
488
+ // - keep original simple selector if returned value is true
489
+ const result = onSpecialAttributeSelector(name, value);
490
+ if (typeof result !== 'boolean') {
491
+ convertedSelectors.push(result);
492
+ continue;
493
+ }
494
+ else if (result === false) {
495
+ continue;
496
+ }
497
+ break;
498
+ }
499
+ case 'PseudoClassSelector': {
500
+ // Not a special pseudo-class selector - clone as-is after the switch
501
+ if (!SUPPORTED_ADG_PSEUDO_CLASSES.has(selector.name.value)
502
+ && !SUPPORTED_UBO_PSEUDO_CLASSES.has(selector.name.value)) {
503
+ break;
504
+ }
505
+ // Throw an error if argument is missing
506
+ if (!selector.argument || selector.argument.value === EMPTY) {
507
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.SPECIAL_PSEUDO_CLASS_SELECTOR_ARGUMENT_REQUIRED, selector.name.value));
508
+ }
509
+ const name = selector.name.value;
510
+ const argument = selector.argument.value;
511
+ // Invoke callback and:
512
+ // - add returned simple selector if it's not boolean
513
+ // - skip adding if returned value is false
514
+ // - keep original simple selector if returned value is true
515
+ const result = onSpecialPseudoClassSelector(name, argument);
516
+ if (typeof result !== 'boolean') {
517
+ convertedSelectors.push(result);
518
+ continue;
519
+ }
520
+ else if (result === false) {
521
+ continue;
522
+ }
523
+ break;
488
524
  }
489
525
  }
490
- else if (attr === AttributeSelectors.TagContent) {
491
- const unescapedValue = unescapeDoubleQuotes(value);
492
- const valueWithoutQuotes = QuoteUtils.removeQuotes(unescapedValue);
493
- convertedSelector.push(addPseudoClassWithValue(PseudoClasses.HasText, valueWithoutQuotes));
494
- }
495
- else {
496
- convertedSelector.push(source.slice(start, end));
526
+ // Clone selector if previous conditions are not met
527
+ convertedSelectors.push(HtmlRuleConverter.cloneSelector(selector));
528
+ }
529
+ convertedComplexSelectors.push({
530
+ type: 'ComplexSelector',
531
+ children: convertedSelectors,
532
+ });
533
+ }
534
+ let convertedBody = {
535
+ type: 'HtmlFilteringRuleBody',
536
+ selectorList: {
537
+ type: 'SelectorList',
538
+ children: convertedComplexSelectors,
539
+ },
540
+ };
541
+ // Convert back to Value if the original body was Value
542
+ if (body.type === 'Value') {
543
+ convertedBody = {
544
+ type: 'Value',
545
+ value: generator.generate(convertedBody),
546
+ };
547
+ }
548
+ return convertedBody;
549
+ }
550
+ /**
551
+ * Clones a simple selector or selector combinator node.
552
+ *
553
+ * @param selector Simple selector or selector combinator node to clone.
554
+ *
555
+ * @returns Cloned simple selector or selector combinator node.
556
+ */
557
+ static cloneSelector(selector) {
558
+ const { type } = selector;
559
+ switch (type) {
560
+ case 'TypeSelector':
561
+ case 'IdSelector':
562
+ case 'ClassSelector':
563
+ return {
564
+ type: selector.type,
565
+ value: selector.value,
566
+ };
567
+ case 'SelectorCombinator':
568
+ return {
569
+ type: selector.type,
570
+ value: selector.value,
571
+ };
572
+ case 'AttributeSelector': {
573
+ const attributeSelectorClone = {
574
+ type: selector.type,
575
+ name: {
576
+ type: selector.name.type,
577
+ value: selector.name.value,
578
+ },
579
+ };
580
+ if ('value' in selector && selector.value) {
581
+ attributeSelectorClone.operator = {
582
+ type: selector.operator.type,
583
+ value: selector.operator.value,
584
+ };
585
+ attributeSelectorClone.value = {
586
+ type: selector.value.type,
587
+ value: selector.value.value,
588
+ };
589
+ if (selector.flag) {
590
+ attributeSelectorClone.flag = {
591
+ type: selector.flag.type,
592
+ value: selector.flag.value,
593
+ };
594
+ }
497
595
  }
596
+ return attributeSelectorClone;
498
597
  }
499
- else if (token.type === TokenType.Whitespace) {
500
- stream.advance();
598
+ case 'PseudoClassSelector': {
599
+ const pseudoClassSelectorClone = {
600
+ type: selector.type,
601
+ name: {
602
+ type: selector.name.type,
603
+ value: selector.name.value,
604
+ },
605
+ };
606
+ if (selector.argument) {
607
+ pseudoClassSelectorClone.argument = {
608
+ type: selector.argument.type,
609
+ value: selector.argument.value,
610
+ };
611
+ }
612
+ return pseudoClassSelectorClone;
501
613
  }
502
- else {
503
- throw new RuleConversionError(sprintf(ERROR_MESSAGES.UNEXPECTED_TOKEN_WITH_VALUE, getFormattedTokenName(token.type), source.slice(token.start, token.end)));
614
+ default: {
615
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, sprintf(ERROR_MESSAGES.UNKNOWN_SELECTOR_TYPE, type)));
504
616
  }
505
617
  }
506
- // Handle min length conversions
507
- if (minTextLength !== undefined) {
508
- convertedSelector.push(addPseudoClassWithValue(PseudoClasses.MinTextLength, minTextLength));
618
+ }
619
+ /**
620
+ * Creates a CSS pseudo-class selector node.
621
+ *
622
+ * @param name The name of the pseudo-class selector.
623
+ * @param argument Optional argument of the pseudo-class selector.
624
+ *
625
+ * @returns CSS pseudo-class selector node.
626
+ */
627
+ static getPseudoClassSelectorNode(name, argument) {
628
+ return {
629
+ type: 'PseudoClassSelector',
630
+ name: {
631
+ type: 'Value',
632
+ value: name,
633
+ },
634
+ argument: argument ? {
635
+ type: 'Value',
636
+ value: argument,
637
+ } : undefined,
638
+ };
639
+ }
640
+ /**
641
+ * Asserts that the given array is not empty.
642
+ *
643
+ * @param array Array to check.
644
+ * @param errorMessage Error message to use if the array is empty.
645
+ *
646
+ * @throws If the array is empty.
647
+ */
648
+ static assertNotEmpty(array, errorMessage) {
649
+ if (array.length === 0) {
650
+ throw new RuleConversionError(sprintf(ERROR_MESSAGES.INVALID_RULE, errorMessage));
651
+ }
652
+ }
653
+ /**
654
+ * Asserts that the given special attribute / pseudo-class length value is valid.
655
+ *
656
+ * @param name Name of the attribute or pseudo-class.
657
+ * @param value Value to parse.
658
+ * @param notIntErrorMessage Error message when the value is not an integer.
659
+ * @param notPositiveErrorMessage Error message when the value is not positive.
660
+ *
661
+ * @throws If the value is not a valid number or not positive.
662
+ */
663
+ static assertValidLengthValue(name, value, notIntErrorMessage, notPositiveErrorMessage) {
664
+ const parsed = Number(value);
665
+ if (Number.isNaN(parsed)) {
666
+ throw new RuleConversionError(sprintf(notIntErrorMessage, name, value));
667
+ }
668
+ if (parsed < 0) {
669
+ throw new RuleConversionError(sprintf(notPositiveErrorMessage, name, value));
509
670
  }
510
- // Combine all selectors
511
- const uboSelector = `${convertedSelector.join(EMPTY)}`;
512
- return createNodeConversionResult([{
513
- category: RuleCategory.Cosmetic,
514
- type: CosmeticRuleType.HtmlFilteringRule,
515
- syntax: AdblockSyntax.Ubo,
516
- exception: rule.exception,
517
- domains: cloneDomainListNode(rule.domains),
518
- separator: {
519
- type: 'Value',
520
- value: rule.exception
521
- ? `${CosmeticRuleSeparator.ElementHidingException}${UBO_HTML_MASK}`
522
- : `${CosmeticRuleSeparator.ElementHiding}${UBO_HTML_MASK}`,
523
- },
524
- body: {
525
- type: 'Value',
526
- value: unescapeDoubleQuotes(uboSelector),
527
- },
528
- }], true);
529
671
  }
530
672
  }
531
673