ssc_codegen 0.13.0__tar.gz → 0.13.1__tar.gz

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.

Potentially problematic release.


This version of ssc_codegen might be problematic. Click here for more details.

Files changed (62) hide show
  1. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/PKG-INFO +1 -1
  2. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/pyproject.toml +1 -1
  3. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/__init__.py +1 -1
  4. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/__init__.py +0 -1
  5. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/base.py +183 -24
  6. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/js_pure.py +4 -5
  7. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/py_base.py +1 -1
  8. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/py_selectolax.py +1 -1
  9. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/static_checker/base.py +0 -1
  10. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/.gitignore +0 -0
  11. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/LICENSE +0 -0
  12. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/README.md +0 -0
  13. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/_compat.py +0 -0
  14. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/base.py +0 -0
  15. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_array.py +0 -0
  16. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_cast.py +0 -0
  17. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_core.py +0 -0
  18. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_filter.py +0 -0
  19. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_selectors.py +0 -0
  20. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_string.py +0 -0
  21. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_/nodes_validate.py +0 -0
  22. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_build/__init__.py +0 -0
  23. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_build/builder.py +0 -0
  24. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_build/main.py +0 -0
  25. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_build/utils.py +0 -0
  26. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_grep_rules/js_rules.yml +0 -0
  27. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/ast_grep_rules/py_rules.yml +0 -0
  28. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/__init__.py +0 -0
  29. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/ast_grep.py +0 -0
  30. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/cli_callbacks.py +0 -0
  31. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/cli_utils.py +0 -0
  32. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/code_callbacks.py +0 -0
  33. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/consts.py +0 -0
  34. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/main.py +0 -0
  35. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
  36. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/compiler.py +0 -0
  37. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/__init__.py +0 -0
  38. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/go_goquery.py +0 -0
  39. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/helpers.py +0 -0
  40. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/lua_htmlparser.py +0 -0
  41. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/py_bs4.py +0 -0
  42. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/py_lxml.py +0 -0
  43. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/py_parsel.py +0 -0
  44. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/__init__.py +0 -0
  45. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/go_goquery.py +0 -0
  46. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/js_pure.py +0 -0
  47. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/lua_base.py +0 -0
  48. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/lua_css_compat.py +0 -0
  49. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/lua_re_compat.py +0 -0
  50. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/converters/templates/py_base.py +0 -0
  51. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/document.py +0 -0
  52. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/document_utlis.py +0 -0
  53. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/json_struct.py +0 -0
  54. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/json_to_scc.py +0 -0
  55. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/logs.py +0 -0
  56. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/pseudo_selectors.py +0 -0
  57. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/schema.py +0 -0
  58. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/selector_utils.py +0 -0
  59. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/static_checker/__init__.py +0 -0
  60. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/static_checker/callbacks.py +0 -0
  61. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/str_utils.py +0 -0
  62. {ssc_codegen-0.13.0 → ssc_codegen-0.13.1}/ssc_codegen/tokens.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssc_codegen
3
- Version: 0.13.0
3
+ Version: 0.13.1
4
4
  Summary: Python-dsl code converter to html parser for web scraping
5
5
  Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
6
6
  Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ssc_codegen"
3
- version = "0.13.0"
3
+ version = "0.13.1"
4
4
  description = "Python-dsl code converter to html parser for web scraping "
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -7,7 +7,7 @@ from ssc_codegen.schema import ItemSchema, DictSchema, ListSchema, FlatListSchem
7
7
 
8
8
  setup_logger()
9
9
 
10
- VERSION = "0.13.0"
10
+ VERSION = "0.13.1"
11
11
 
12
12
 
13
13
  class __MISSING(object):
@@ -120,7 +120,6 @@ from .nodes_validate import (
120
120
  ExprIsEqual,
121
121
  ExprStringIsRegex,
122
122
  ExprIsXpath,
123
- ExprIsNotEqual,
124
123
  ExprIsContains,
125
124
  ExprListStringAnyRegex,
126
125
  ExprListStringAllRegex,
@@ -28,6 +28,18 @@ CB_AST_DECORATOR = Callable[[CB_AST_BIND], CB_AST_BIND]
28
28
 
29
29
 
30
30
  def debug_comment_cb(node: BaseAstNode, comment_prefix: str) -> str:
31
+ """Generate a debug comment for an AST node.
32
+
33
+ Creates a formatted string containing information about the node's type,
34
+ return type (if applicable), and other relevant details for debugging purposes.
35
+
36
+ Args:
37
+ node: The AST node to generate a comment for.
38
+ comment_prefix: The prefix to add to each line of the comment.
39
+
40
+ Returns:
41
+ A formatted string containing debug information about the node.
42
+ """
31
43
  match node.kind:
32
44
  case TokenType.EXPR_RETURN:
33
45
  token = f"{comment_prefix}Token: {node.kind.name} ret_type: {node.ret_type.name}"
@@ -48,16 +60,60 @@ def debug_comment_cb(node: BaseAstNode, comment_prefix: str) -> str:
48
60
 
49
61
 
50
62
  class BaseCodeConverter:
51
- """base ast visitor class and code converter
52
-
53
- Visitor is set using decorators pointing to the TokenType
54
-
55
- decorators hooks can be overridden
56
-
57
- - as defaults __call__ methods - set @pre decorator
58
- - @pre - first trigger visitor entrypoint
59
- - @post - second trigger visitor entrypoint (for close brackets or other logic, for example)
60
-
63
+ """Base AST visitor class and code converter.
64
+
65
+ This class serves as the foundation for converting AST nodes into code.
66
+ Visitors are set using decorators that point to specific TokenTypes.
67
+
68
+ Decorator hooks can be overridden:
69
+ - As defaults, __call__ methods set @pre decorator
70
+ - @pre - First trigger visitor entrypoint
71
+ - @post - Second trigger visitor entrypoint (for closing brackets or other logic).
72
+ It will be called when the broadcast of all nodes and the container body is transpiled.
73
+
74
+ Example:
75
+ ```
76
+ CONVERTER = BaseCodeConverter()
77
+
78
+ # recommended use <node>.kind call as key in decorators instead TokenType.<TOKEN>
79
+ @CONVERTER(ModuleImports.kind)
80
+ def pre_imports(_node: ModuleImports) -> str:
81
+ # implement imports stmt
82
+ return '''from bs4 import BeautifulSoap'''
83
+
84
+ # TypeDef and StartParseMethod node allow add extra second shortcut as StructType for
85
+ # increase code readability and simplify codegen
86
+
87
+ # TYPEDEF Impl
88
+ # post_callback - shortcut for minify typical end expr (eg: close parens)
89
+
90
+ # generate type for StructType.ITEM parser
91
+ @CONVERTER(TypeDef.kind, StructType.ITEM, post_callback=lambda _: "})")
92
+ def pre_typedef_item(node: TypeDef) -> str:
93
+ name, _ = node.unpack_args()
94
+ return f'T_{name} = TypedDict("T_{name}", ' + "{"
95
+
96
+ # generate type for StructType.DICT parser
97
+ @CONVERTER(TypeDef.kind, StructType.DICT)
98
+ def pre_typedef_dict(node: TypeDef) -> str:
99
+ name, _ = node.unpack_args()
100
+ type_ = get_typedef_field_by_name(node, "__VALUE__")
101
+ return f"T_{name} = Dict[str, {type_}]"
102
+
103
+ # StartParse impl
104
+ @CONVERTER(StartParseMethod.kind, StructType.ITEM)
105
+ def pre_start_parse_item(node: StartParseMethod) -> str:
106
+ # some impl for ItEM struct
107
+ return ""
108
+
109
+ @CONVERTER(StartParseMethod.kind, StructType.LIST)
110
+ def pre_start_parse_list(node: StartParseMethod) -> str:
111
+ # some impl for LIST struct
112
+ return ""
113
+ ```
114
+
115
+ Attributes:
116
+ TEST_EXCLUDE_NODES: List of token types to exclude from tests.
61
117
  """
62
118
 
63
119
  TEST_EXCLUDE_NODES: ClassVar[list[TokenType]] = [
@@ -70,7 +126,7 @@ class BaseCodeConverter:
70
126
  TokenType.STRUCT_CALL_FUNCTION,
71
127
  TokenType.STRUCT_CALL_CLASSVAR,
72
128
  ]
73
- """dev classvar marks exclude nodes in tests"""
129
+ """Developer class variable that marks nodes to exclude in tests."""
74
130
 
75
131
  def __init__(
76
132
  self,
@@ -80,10 +136,10 @@ class BaseCodeConverter:
80
136
  debug_cb: CB_FMT_DEBUG_COMMENT = debug_comment_cb,
81
137
  ) -> None:
82
138
  """
83
-
84
- :param debug_instructions: enable debug instructions (add comment for every generated instruction)
85
- :param debug_comment_prefix: comment line prefix
86
- :param debug_cb: callback formatting comment
139
+ Arguments:
140
+ debug_instructions: enable debug instructions (add comment for every generated instruction. used in debug codegen output)
141
+ debug_comment_prefix: comment line prefix (used in debug codegen output)
142
+ debug_cb: callback formatting comment (used in debug codegen output)
87
143
  """
88
144
  self._debug_instructions = debug_instructions
89
145
  self._debug_comment_prefix = debug_comment_prefix
@@ -131,14 +187,14 @@ class BaseCodeConverter:
131
187
 
132
188
  optional allow set post callback for simple string casts like close brackets etc
133
189
 
134
- StartParseMethod has shortcut (StartParseMethod.kind, StructType.<TYPE>) for simplify callback generators
190
+ StartParseMethod and TypeDef nodes has shortcut (StartParseMethod.kind, StructType.<TYPE>) for simplify callback generators
135
191
  """
136
192
  if for_struct_definition and for_definition not in (
137
193
  StartParseMethod.kind,
138
194
  TypeDef.kind,
139
195
  ):
140
196
  raise TypeError(
141
- "Add struct definition allowed only for `StartParseMethod` nodes"
197
+ "Add struct definition allowed only for `StartParseMethod` and `TypeDef` nodes"
142
198
  )
143
199
 
144
200
  def decorator(func: CB_AST_BIND) -> CB_AST_BIND:
@@ -164,7 +220,23 @@ class BaseCodeConverter:
164
220
  for_definition: TokenType,
165
221
  for_struct_definition: StructType | None = None,
166
222
  ) -> CB_AST_DECORATOR:
167
- """Define a post-conversion decorator for the given TokenType."""
223
+ """Define a post-conversion decorator for the given TokenType.
224
+
225
+ This method creates a decorator that registers a function to be called
226
+ after the main conversion of a node of the specified type. It's typically
227
+ used for closing brackets or other logic that should happen after the
228
+ main node processing.
229
+
230
+ Args:
231
+ for_definition: The TokenType to associate with this post-conversion function.
232
+ for_struct_definition: Optional StructType for specific struct definitions (for StartParseMethod and TypeDef).
233
+
234
+ Returns:
235
+ A decorator function that registers the post-conversion handler.
236
+
237
+ Raises:
238
+ TypeError: If struct definition is provided for unsupported node types.
239
+ """
168
240
  if for_struct_definition and for_definition not in (
169
241
  StartParseMethod.kind,
170
242
  TypeDef.kind,
@@ -191,7 +263,19 @@ class BaseCodeConverter:
191
263
  *,
192
264
  post_callback: Callable[[BaseAstNode], str] | None = None,
193
265
  ) -> CB_AST_DECORATOR:
194
- """Alias for pre decorator."""
266
+ """Alias for pre decorator.
267
+
268
+ Provides a convenient way to use the pre decorator by calling the class instance
269
+ directly. This is equivalent to calling the pre() method.
270
+
271
+ Args:
272
+ for_definition: The TokenType to associate with this conversion function.
273
+ for_struct_definition: Optional StructType for specific struct definitions (only for StartParseMethod and TypeDef nodes).
274
+ post_callback: Optional callback function for post-processing.
275
+
276
+ Returns:
277
+ A decorator function that registers the conversion handler.
278
+ """
195
279
  return self.pre(
196
280
  for_definition, for_struct_definition, post_callback=post_callback
197
281
  )
@@ -199,7 +283,20 @@ class BaseCodeConverter:
199
283
  def _pre_convert_node(
200
284
  self, node: BaseAstNode, st_type: StructType | None = None
201
285
  ) -> str:
202
- """Convert the AST node using the pre-definition function."""
286
+ """Convert the AST node using the pre-definition function.
287
+
288
+ This method applies the registered pre-conversion function to an AST node,
289
+ optionally using a specific struct type for more granular control. It handles
290
+ both simple node conversions and struct-specific conversions.
291
+
292
+ Args:
293
+ node: The AST node to convert.
294
+ st_type: Optional StructType for struct-specific conversions.
295
+
296
+ Returns:
297
+ The result of applying the pre-conversion function to the node,
298
+ or an empty string if no matching function is found.
299
+ """
203
300
  # syntax sugar: split generate start_parse methods by part callbacks
204
301
  if (
205
302
  node.kind in (StartParseMethod.kind, TypeDef.kind)
@@ -220,7 +317,21 @@ class BaseCodeConverter:
220
317
  def _post_convert_node(
221
318
  self, node: BaseAstNode, st_type: StructType | None = None
222
319
  ) -> str:
223
- """Convert the AST node using the post-definition function."""
320
+ """Convert the AST node using the post-definition function.
321
+
322
+ This method applies the registered post-conversion function to an AST node,
323
+ optionally using a specific struct type for more granular control. It's typically
324
+ used for closing brackets or other logic that should happen after the main
325
+ node processing.
326
+
327
+ Args:
328
+ node: The AST node to convert.
329
+ st_type: Optional StructType for struct-specific conversions.
330
+
331
+ Returns:
332
+ The result of applying the post-conversion function to the node,
333
+ or an empty string if no matching function is found.
334
+ """
224
335
  # syntax sugar: split generate start_parse methods by part callbacks
225
336
  if (
226
337
  node.kind in (StartParseMethod.kind, TypeDef.kind)
@@ -239,7 +350,19 @@ class BaseCodeConverter:
239
350
  def convert_program(
240
351
  self, ast_program: ModuleProgram, comment: str = ""
241
352
  ) -> list[str]:
242
- """Convert the module AST to code parts."""
353
+ """Convert the module AST to code parts.
354
+
355
+ This method converts an entire module AST into a list of code parts,
356
+ starting with an optional comment and processing all nodes in the AST.
357
+
358
+ Args:
359
+ ast_program: The ModuleProgram AST node to convert.
360
+ comment: An optional comment to include at the beginning of the output.
361
+
362
+ Returns:
363
+ A list of strings representing the converted code parts,
364
+ with empty strings filtered out.
365
+ """
243
366
  acc = [comment]
244
367
  result = self.convert(ast_program, acc)
245
368
  return [i for i in result if i]
@@ -247,7 +370,19 @@ class BaseCodeConverter:
247
370
  def convert(
248
371
  self, ast_entry: BaseAstNode, acc: list[str] | None = None
249
372
  ) -> list[str]:
250
- """Convert an AST node into code parts."""
373
+ """Convert an AST node into code parts.
374
+
375
+ This method dispatches the conversion of an AST node to the appropriate
376
+ conversion method based on the node's type. It builds up a list of code
377
+ parts by processing the node and its children.
378
+
379
+ Args:
380
+ ast_entry: The AST node to convert.
381
+ acc: An optional accumulator list to append code parts to.
382
+
383
+ Returns:
384
+ A list of strings representing the converted code parts.
385
+ """
251
386
  acc = acc or []
252
387
  match ast_entry.kind:
253
388
  case TokenType.MODULE:
@@ -307,6 +442,22 @@ class BaseCodeConverter:
307
442
  def _convert_logic_filter(
308
443
  self, ast_entry: FilterOr | FilterAnd | FilterNot, acc: list[str]
309
444
  ) -> None:
445
+ """Convert a logical filter node (OR, AND, NOT) and its children.
446
+
447
+ This method handles the conversion of logical filter operations, appending
448
+ the pre-conversion result, processing all child nodes, and then appending
449
+ the post-conversion result.
450
+
451
+ For example, an AND filter with two children would be converted as:
452
+ AND(body=[F1, F2]) ->
453
+ AND ( (OPEN)
454
+ F1 and F2
455
+ ) (CLOSE)
456
+
457
+ Args:
458
+ ast_entry: The logical filter node (FilterOr, FilterAnd, or FilterNot) to convert.
459
+ acc: The accumulator list to append code parts to.
460
+ """
310
461
  # AND(body=[F1, F2]) ->
311
462
  # AND ( (OPEN)
312
463
  # F1 and F2
@@ -317,7 +468,15 @@ class BaseCodeConverter:
317
468
  acc.append(self._post_convert_node(ast_entry))
318
469
 
319
470
  def _convert_module(self, ast_entry: ModuleProgram, acc: list[str]) -> None:
320
- """Handle module conversion."""
471
+ """Handle module conversion.
472
+
473
+ This method processes all nodes in a module's body by recursively converting
474
+ each one and appending the results to the accumulator.
475
+
476
+ Args:
477
+ ast_entry: The ModuleProgram node to convert.
478
+ acc: The accumulator list to append code parts to.
479
+ """
321
480
  for node in ast_entry.body:
322
481
  self.convert(node, acc)
323
482
 
@@ -51,7 +51,6 @@ from ssc_codegen.ast_ import (
51
51
  ExprIndex,
52
52
  ExprListStringJoin,
53
53
  ExprIsEqual,
54
- ExprIsNotEqual,
55
54
  ExprIsContains,
56
55
  ExprStringIsRegex,
57
56
  ExprToInt,
@@ -848,7 +847,7 @@ def pre_is_css(node: ExprIsCss) -> str:
848
847
  expr = f"({prv}.querySelector({query}) === null)"
849
848
  if invert:
850
849
  expr = f"!{expr}"
851
- expr = f'if {expr}'
850
+ expr = f"if {expr}"
852
851
  expr = f"{expr} throw new Error({msg});"
853
852
  if is_last_var_no_ret(node):
854
853
  return expr
@@ -865,7 +864,7 @@ def pre_is_xpath(node: ExprIsXpath) -> str:
865
864
  expr = f"(document.evaluate({query}, {prv}, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue === null)"
866
865
  if invert:
867
866
  expr = f"!{expr}"
868
- expr = f'if {expr}'
867
+ expr = f"if {expr}"
869
868
  code = [
870
869
  f"{expr}",
871
870
  "{",
@@ -1076,8 +1075,8 @@ def pre_has_attr(node: ExprHasAttr) -> str:
1076
1075
  invert = node.kwargs["invert"]
1077
1076
  expr = f"!{prv}?.hasAttribute({key})"
1078
1077
  if invert:
1079
- expr = f'!({expr})'
1080
- expr = f"if ({expr})"
1078
+ expr = f"!({expr})"
1079
+ expr = f"if ({expr}) throw new Error({msg}); "
1081
1080
  if is_last_var_no_ret(node):
1082
1081
  return expr
1083
1082
  return expr + f"let {nxt} = {prv};"
@@ -963,7 +963,7 @@ def pre_is_contains(node: ExprIsContains) -> str:
963
963
 
964
964
  item = py_get_classvar_hook_or_value(node, "item")
965
965
  msg = py_get_classvar_hook_or_value(node, "msg")
966
-
966
+
967
967
  if invert:
968
968
  expr = f"{item} not in {prv}"
969
969
  else:
@@ -211,7 +211,7 @@ def pre_is_css(node: ExprIsCss) -> str:
211
211
  msg = py_get_classvar_hook_or_value(node, "msg")
212
212
  invert = node.kwargs["invert"]
213
213
 
214
- expr = {prv}.css_first({query})
214
+ expr = f"{prv}.css_first({query})"
215
215
  if invert:
216
216
  expr = f"not {expr}"
217
217
 
@@ -58,7 +58,6 @@ FMT_MAPPING_METHODS = {
58
58
  TokenType.EXPR_LIST_JOIN: "join",
59
59
  TokenType.EXPR_LIST_LEN: "to_len",
60
60
  TokenType.IS_EQUAL: "is_equal",
61
- TokenType.IS_NOT_EQUAL: "is_not_equal",
62
61
  TokenType.IS_CONTAINS: "is_contains",
63
62
  TokenType.IS_CSS: "is_css",
64
63
  TokenType.IS_XPATH: "is_xpath",
File without changes
File without changes
File without changes