ssc_codegen 0.29.5__tar.gz → 0.30.0__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.
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/PKG-INFO +3 -3
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/pyproject.toml +3 -5
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/js_pure.py +1 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/py_bs4.py +5 -11
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/py_lxml.py +3 -3
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/__init__.py +3 -3
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/contexts.py +197 -235
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/expressions.py +4 -13
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/format.py +361 -361
- ssc_codegen-0.30.0/ssc_codegen/core/linter.py +1491 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/module_handler.py +9 -49
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/predicates.py +2 -2
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/reader.py +116 -164
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/struct_parser.py +17 -41
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/core/type_checking.py +401 -401
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/kdl/__init__.py +8 -5
- ssc_codegen-0.29.5/ssc_codegen/core/adapter.py +0 -25
- ssc_codegen-0.29.5/ssc_codegen/core/linting.py +0 -1179
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/.gitignore +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/LICENSE +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/README.md +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/__init__.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/_logging.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/__init__.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/array.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/base.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/cast.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/control.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/extract.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/helpers.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/jsondef.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/module.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/predicate_containers.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/predicate_ops.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/regex.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/selectors.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/string.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/struct.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/transform.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/typedef.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/ast/types.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/py_render.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/py_slax.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/converters/request_spec.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/document_utils.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/exceptions.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/health.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/kdl/dict_reader.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/kdl/parser.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/kdl/reader.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/main.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/parsers/__init__.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/parsers/curl.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/parsers/http.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/pseudo_selectors.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/regex_utils.py +0 -0
- {ssc_codegen-0.29.5 → ssc_codegen-0.30.0}/ssc_codegen/selector_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.0
|
|
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
|
|
@@ -20,13 +20,13 @@ Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
|
20
20
|
Classifier: Topic :: Utilities
|
|
21
21
|
Requires-Python: >=3.10
|
|
22
22
|
Requires-Dist: bs4>=0.0.2
|
|
23
|
-
Requires-Dist: click<8.2.0
|
|
24
23
|
Requires-Dist: colorama>=0.4.6; sys_platform == 'win32'
|
|
25
24
|
Requires-Dist: cssselect>=1.2.0
|
|
25
|
+
Requires-Dist: kdlquery>=1.1.2
|
|
26
26
|
Requires-Dist: lxml>=5.3.0
|
|
27
27
|
Requires-Dist: pyyaml>=6.0
|
|
28
28
|
Requires-Dist: soupsieve>=2.6
|
|
29
|
-
Requires-Dist: typer
|
|
29
|
+
Requires-Dist: typer>0.26
|
|
30
30
|
Requires-Dist: typing-extensions; python_version < '3.11'
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ssc_codegen"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.30.0"
|
|
4
4
|
description = "Python-dsl code converter to html parser for web scraping "
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -10,12 +10,10 @@ dependencies = [
|
|
|
10
10
|
"cssselect>=1.2.0",
|
|
11
11
|
"lxml>=5.3.0",
|
|
12
12
|
"soupsieve>=2.6",
|
|
13
|
-
"typer
|
|
13
|
+
"typer>0.26",
|
|
14
14
|
"typing_extensions;python_version<'3.11'",
|
|
15
|
-
# https://github.com/fastapi/typer/discussions/1215
|
|
16
|
-
# https://github.com/fastapi/typer/pull/1145
|
|
17
|
-
"click<8.2.0",
|
|
18
15
|
"pyyaml>=6.0",
|
|
16
|
+
"kdlquery>=1.1.2",
|
|
19
17
|
]
|
|
20
18
|
|
|
21
19
|
classifiers = [
|
|
@@ -307,7 +307,7 @@ def pre_utilities(node: a.Utilities, ctx: ConverterContext):
|
|
|
307
307
|
runtime = ctx.meta.get("runtime_module")
|
|
308
308
|
if runtime:
|
|
309
309
|
names = runtime_export_names(node)
|
|
310
|
-
return [f"from {runtime} import " + ", ".join(names), ""]
|
|
310
|
+
return [f"from .{runtime} import " + ", ".join(names), ""]
|
|
311
311
|
|
|
312
312
|
if module_is_rest_only(node):
|
|
313
313
|
lines = []
|
|
@@ -808,16 +808,10 @@ def pre_struct_check_method(node: a.CheckMethod, ctx: ConverterContext):
|
|
|
808
808
|
from ssc_codegen.converters.helpers import to_snake_case
|
|
809
809
|
|
|
810
810
|
method_name = to_snake_case(node.name)
|
|
811
|
-
return [
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
# def post_struct_check_method(node: a.CheckMethod, ctx: ConverterContext):
|
|
816
|
-
# return [
|
|
817
|
-
# " return True",
|
|
818
|
-
# " except Exception:",
|
|
819
|
-
# " return False",
|
|
820
|
-
# ]
|
|
811
|
+
return [
|
|
812
|
+
f" def {method_name}(self) -> bool:",
|
|
813
|
+
f" {ctx.var_name} = self._doc",
|
|
814
|
+
]
|
|
821
815
|
|
|
822
816
|
|
|
823
817
|
@PY_BASE_CONVERTER(a.SplitDoc)
|
|
@@ -87,7 +87,7 @@ def pre_utilities(node: a.Utilities, ctx: ConverterContext):
|
|
|
87
87
|
if runtime:
|
|
88
88
|
names = py_bs4.runtime_export_names(node)
|
|
89
89
|
names.append(_FALLBACK_HTML_EXPORT)
|
|
90
|
-
return [f"from {runtime} import " + ", ".join(names), ""]
|
|
90
|
+
return [f"from .{runtime} import " + ", ".join(names), ""]
|
|
91
91
|
|
|
92
92
|
if py_bs4.module_is_rest_only(node):
|
|
93
93
|
lines = []
|
|
@@ -352,7 +352,7 @@ def pre_expr_attr(node: a.Attr, ctx: ConverterContext):
|
|
|
352
352
|
@PY_LXML_CONVERTER(a.PredCss)
|
|
353
353
|
def pre_expr_pred_css(node: a.PredCss, ctx: ConverterContext):
|
|
354
354
|
query = repr(node.query)
|
|
355
|
-
cond = f"
|
|
355
|
+
cond = f"i.cssselect({query}) is not None"
|
|
356
356
|
if ctx.index == 0:
|
|
357
357
|
return ctx.indent + cond
|
|
358
358
|
return ctx.indent + f"and {cond}"
|
|
@@ -361,7 +361,7 @@ def pre_expr_pred_css(node: a.PredCss, ctx: ConverterContext):
|
|
|
361
361
|
@PY_LXML_CONVERTER(a.PredXpath)
|
|
362
362
|
def pre_expr_pred_xpath(node: a.PredXpath, ctx: ConverterContext):
|
|
363
363
|
query = repr(node.query)
|
|
364
|
-
cond = f"
|
|
364
|
+
cond = f"i.xpath({query}) is not None"
|
|
365
365
|
if ctx.index == 0:
|
|
366
366
|
return ctx.indent + cond
|
|
367
367
|
return ctx.indent + f"and {cond}"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""ssc_codegen.core — unified KDL -> Module AST reader with integrated linting."""
|
|
2
|
-
from ssc_codegen.core.reader import parse_module
|
|
2
|
+
from ssc_codegen.core.reader import parse_module
|
|
3
3
|
from ssc_codegen.core.format import format_diagnostics, format_diagnostic
|
|
4
|
+
from kdlquery import ReadDiagnostic
|
|
4
5
|
__all__ = [
|
|
5
6
|
"parse_module",
|
|
6
|
-
"SscReader",
|
|
7
7
|
"ReadDiagnostic",
|
|
8
8
|
"format_diagnostics",
|
|
9
9
|
"format_diagnostic"
|
|
10
|
-
]
|
|
10
|
+
]
|
|
@@ -1,235 +1,197 @@
|
|
|
1
|
-
"""Parse/lint contexts, error codes, and supporting data classes."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass, field
|
|
6
|
-
from enum import Enum, auto
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from ssc_codegen.ast import VariableType
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# ── Walk context enum ──────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class WalkCtx(Enum):
|
|
18
|
-
MODULE = auto()
|
|
19
|
-
STRUCT_BODY = auto()
|
|
20
|
-
INIT_BLOCK = auto()
|
|
21
|
-
PIPELINE = auto()
|
|
22
|
-
JSON_TYPEDEF = auto()
|
|
23
|
-
SPECIAL_FIELD = auto()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# ── Error codes ────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ErrorCode(Enum):
|
|
30
|
-
INVALID_SYNTAX = "E000"
|
|
31
|
-
MISSING_ARGUMENT = "E001"
|
|
32
|
-
INVALID_ARGUMENT = "E002"
|
|
33
|
-
EMPTY_BLOCK = "E003"
|
|
34
|
-
UNEXPECTED_CHILDREN = "E004"
|
|
35
|
-
TYPE_MISMATCH = "E100"
|
|
36
|
-
INCOMPATIBLE_OPERATION = "E101"
|
|
37
|
-
UNKNOWN_OPERATION = "E200"
|
|
38
|
-
UNKNOWN_FIELD = "E201"
|
|
39
|
-
MISSING_REQUIRED_FIELD = "E202"
|
|
40
|
-
INVALID_FIELD_FOR_TYPE = "E203"
|
|
41
|
-
UNDEFINED_REFERENCE = "E300"
|
|
42
|
-
INIT_FIELD_NOT_FOUND = "E301"
|
|
43
|
-
DEFINE_NOT_FOUND = "E302"
|
|
44
|
-
INVALID_STRUCT_TYPE = "E400"
|
|
45
|
-
MISSING_SPECIAL_FIELD = "E401"
|
|
46
|
-
DEPRECATED_SYNTAX = "W001"
|
|
47
|
-
UNUSED_FIELD = "W002"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class DefineKind(Enum):
|
|
51
|
-
SCALAR = auto()
|
|
52
|
-
BLOCK = auto()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@dataclass
|
|
56
|
-
class RawArg:
|
|
57
|
-
value: str
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
set(self.
|
|
96
|
-
| set(self.
|
|
97
|
-
| set(self.
|
|
98
|
-
| set(self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def get_arg(self, node: KdlNode, index: int) -> str | None:
|
|
199
|
-
args = self.get_args(node)
|
|
200
|
-
return args[index] if index < len(args) else None
|
|
201
|
-
|
|
202
|
-
def get_prop(self, node: KdlNode, key: str) -> str | None:
|
|
203
|
-
entry = node.properties.get(key)
|
|
204
|
-
return str(entry.value) if entry else None
|
|
205
|
-
|
|
206
|
-
def get_children_nodes(self, node: KdlNode) -> list[KdlNode]:
|
|
207
|
-
return list(node.children)
|
|
208
|
-
|
|
209
|
-
def has_empty_block(self, node: KdlNode) -> bool:
|
|
210
|
-
return len(node.children) == 0
|
|
211
|
-
|
|
212
|
-
def is_define_ref(self, arg: str) -> bool:
|
|
213
|
-
return arg in self.defines
|
|
214
|
-
|
|
215
|
-
def resolve_scalar_arg(self, arg: str) -> str | None:
|
|
216
|
-
info = self.defines.get(arg)
|
|
217
|
-
if info is not None and info.kind == DefineKind.SCALAR:
|
|
218
|
-
return info.value
|
|
219
|
-
return None
|
|
220
|
-
|
|
221
|
-
@property
|
|
222
|
-
def in_predicate(self) -> bool:
|
|
223
|
-
return self._predicate_depth > 0
|
|
224
|
-
|
|
225
|
-
@property
|
|
226
|
-
def in_assert(self) -> bool:
|
|
227
|
-
return self._predicate_depth > 0 and self._predicate_context == "assert"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# lazy imports for forward refs
|
|
231
|
-
from ssc_codegen.ast import ( # noqa: E402
|
|
232
|
-
JsonDef,
|
|
233
|
-
Struct,
|
|
234
|
-
TransformDef,
|
|
235
|
-
)
|
|
1
|
+
"""Parse/lint contexts, error codes, and supporting data classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum, auto
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from ssc_codegen.ast import VariableType
|
|
10
|
+
from kdlquery import KdlNode, ReadDiagnostic, Severity
|
|
11
|
+
from kdlquery.types import Span
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── Walk context enum ──────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WalkCtx(Enum):
|
|
18
|
+
MODULE = auto()
|
|
19
|
+
STRUCT_BODY = auto()
|
|
20
|
+
INIT_BLOCK = auto()
|
|
21
|
+
PIPELINE = auto()
|
|
22
|
+
JSON_TYPEDEF = auto()
|
|
23
|
+
SPECIAL_FIELD = auto()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ── Error codes ────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ErrorCode(Enum):
|
|
30
|
+
INVALID_SYNTAX = "E000"
|
|
31
|
+
MISSING_ARGUMENT = "E001"
|
|
32
|
+
INVALID_ARGUMENT = "E002"
|
|
33
|
+
EMPTY_BLOCK = "E003"
|
|
34
|
+
UNEXPECTED_CHILDREN = "E004"
|
|
35
|
+
TYPE_MISMATCH = "E100"
|
|
36
|
+
INCOMPATIBLE_OPERATION = "E101"
|
|
37
|
+
UNKNOWN_OPERATION = "E200"
|
|
38
|
+
UNKNOWN_FIELD = "E201"
|
|
39
|
+
MISSING_REQUIRED_FIELD = "E202"
|
|
40
|
+
INVALID_FIELD_FOR_TYPE = "E203"
|
|
41
|
+
UNDEFINED_REFERENCE = "E300"
|
|
42
|
+
INIT_FIELD_NOT_FOUND = "E301"
|
|
43
|
+
DEFINE_NOT_FOUND = "E302"
|
|
44
|
+
INVALID_STRUCT_TYPE = "E400"
|
|
45
|
+
MISSING_SPECIAL_FIELD = "E401"
|
|
46
|
+
DEPRECATED_SYNTAX = "W001"
|
|
47
|
+
UNUSED_FIELD = "W002"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DefineKind(Enum):
|
|
51
|
+
SCALAR = auto()
|
|
52
|
+
BLOCK = auto()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class RawArg:
|
|
57
|
+
value: str
|
|
58
|
+
span: Span
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class DefineInfo:
|
|
63
|
+
name: str
|
|
64
|
+
kind: DefineKind
|
|
65
|
+
value: str | None
|
|
66
|
+
node: KdlNode
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class TransformInfo:
|
|
71
|
+
name: str
|
|
72
|
+
accept: str
|
|
73
|
+
ret: str
|
|
74
|
+
langs: list[str]
|
|
75
|
+
node: KdlNode
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ── Parse context ───────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ParseContext:
|
|
83
|
+
property_defines: dict[str, str | int | float | bool] = field(
|
|
84
|
+
default_factory=dict
|
|
85
|
+
)
|
|
86
|
+
children_defines: dict[str, list[KdlNode]] = field(default_factory=dict)
|
|
87
|
+
transforms: dict[str, TransformDef] = field(default_factory=dict)
|
|
88
|
+
structs: dict[str, Struct] = field(default_factory=dict)
|
|
89
|
+
json_defs: dict[str, JsonDef] = field(default_factory=dict)
|
|
90
|
+
source_path: Path | None = None
|
|
91
|
+
|
|
92
|
+
def all_names(self) -> set[str]:
|
|
93
|
+
return (
|
|
94
|
+
set(self.property_defines)
|
|
95
|
+
| set(self.children_defines)
|
|
96
|
+
| set(self.transforms)
|
|
97
|
+
| set(self.structs)
|
|
98
|
+
| set(self.json_defs)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── Lint context ───────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class LintContext:
|
|
107
|
+
"""Lint state for reader-side validation (type inference, per-op checks)."""
|
|
108
|
+
|
|
109
|
+
defines: dict[str, DefineInfo] = field(default_factory=dict)
|
|
110
|
+
transforms: dict[str, TransformInfo] = field(default_factory=dict)
|
|
111
|
+
init_fields: set[str] = field(default_factory=set)
|
|
112
|
+
walk_context: WalkCtx = WalkCtx.MODULE
|
|
113
|
+
_path_segments: list[str] = field(default_factory=list)
|
|
114
|
+
diagnostics: list[ReadDiagnostic] = field(default_factory=list)
|
|
115
|
+
inferred_define_types: dict[str, tuple[VariableType, VariableType]] = field(
|
|
116
|
+
default_factory=dict
|
|
117
|
+
)
|
|
118
|
+
_predicate_depth: int = field(default=0)
|
|
119
|
+
_predicate_context: str = ""
|
|
120
|
+
|
|
121
|
+
def error(
|
|
122
|
+
self,
|
|
123
|
+
node: KdlNode,
|
|
124
|
+
*,
|
|
125
|
+
message: str,
|
|
126
|
+
code: str = "",
|
|
127
|
+
hint: str = "",
|
|
128
|
+
label: str | None = None,
|
|
129
|
+
notes: list[str] | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
self.diagnostics.append(
|
|
132
|
+
ReadDiagnostic(
|
|
133
|
+
message=message,
|
|
134
|
+
severity=Severity.ERROR,
|
|
135
|
+
span=node.span,
|
|
136
|
+
path=self.path,
|
|
137
|
+
hint=hint,
|
|
138
|
+
code=code,
|
|
139
|
+
label=label,
|
|
140
|
+
notes=tuple(notes) if notes else (),
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def warning(
|
|
145
|
+
self,
|
|
146
|
+
node: KdlNode,
|
|
147
|
+
*,
|
|
148
|
+
message: str,
|
|
149
|
+
code: str = "",
|
|
150
|
+
hint: str = "",
|
|
151
|
+
label: str | None = None,
|
|
152
|
+
notes: list[str] | None = None,
|
|
153
|
+
) -> None:
|
|
154
|
+
self.diagnostics.append(
|
|
155
|
+
ReadDiagnostic(
|
|
156
|
+
message=message,
|
|
157
|
+
severity=Severity.WARNING,
|
|
158
|
+
span=node.span,
|
|
159
|
+
path=self.path,
|
|
160
|
+
hint=hint,
|
|
161
|
+
code=code,
|
|
162
|
+
label=label,
|
|
163
|
+
notes=tuple(notes) if notes else (),
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def path(self) -> str:
|
|
169
|
+
return "/".join(self._path_segments)
|
|
170
|
+
|
|
171
|
+
def push(self, segment: str) -> None:
|
|
172
|
+
self._path_segments.append(segment)
|
|
173
|
+
|
|
174
|
+
def pop(self) -> None:
|
|
175
|
+
self._path_segments.pop()
|
|
176
|
+
|
|
177
|
+
def resolve_scalar_arg(self, arg: str) -> str | None:
|
|
178
|
+
info = self.defines.get(arg)
|
|
179
|
+
if info is not None and info.kind == DefineKind.SCALAR:
|
|
180
|
+
return info.value
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def in_predicate(self) -> bool:
|
|
185
|
+
return self._predicate_depth > 0
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def in_assert(self) -> bool:
|
|
189
|
+
return self._predicate_depth > 0 and self._predicate_context == "assert"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# lazy imports for forward refs
|
|
193
|
+
from ssc_codegen.ast import ( # noqa: E402
|
|
194
|
+
JsonDef,
|
|
195
|
+
Struct,
|
|
196
|
+
TransformDef,
|
|
197
|
+
)
|
|
@@ -76,12 +76,12 @@ from ssc_codegen.ast import (
|
|
|
76
76
|
XpathSelectAll,
|
|
77
77
|
)
|
|
78
78
|
from ssc_codegen.exceptions import BuildTimeError
|
|
79
|
-
from
|
|
79
|
+
from kdlquery import KdlNode
|
|
80
80
|
from ssc_codegen.regex_utils import normalize_regex_pattern
|
|
81
81
|
from typing import cast
|
|
82
82
|
|
|
83
83
|
from ssc_codegen.core.contexts import LintContext, ParseContext, WalkCtx
|
|
84
|
-
from ssc_codegen.core.
|
|
84
|
+
from ssc_codegen.core.linter import (
|
|
85
85
|
lint_pipeline_op,
|
|
86
86
|
lint_validate_css,
|
|
87
87
|
lint_validate_xpath,
|
|
@@ -866,12 +866,7 @@ def _expr_len(
|
|
|
866
866
|
def _expr_unique(
|
|
867
867
|
node: KdlNode, parent: FieldLikeNode, ctx: ParseContext, lint: LintContext
|
|
868
868
|
):
|
|
869
|
-
keep_order = bool(
|
|
870
|
-
node.properties.get(
|
|
871
|
-
"keep-order",
|
|
872
|
-
KdlArg(value=False, span=node.span, is_identifier=False),
|
|
873
|
-
).value
|
|
874
|
-
)
|
|
869
|
+
keep_order = bool(node.get_prop("keep-order") or False)
|
|
875
870
|
return Unique(parent=parent, keep_order=keep_order)
|
|
876
871
|
|
|
877
872
|
|
|
@@ -917,11 +912,7 @@ def _expr_jsonify(
|
|
|
917
912
|
node: KdlNode, parent: FieldLikeNode, ctx: ParseContext, lint: LintContext
|
|
918
913
|
):
|
|
919
914
|
schema_name = str(node.args[0].value)
|
|
920
|
-
path =
|
|
921
|
-
node.properties.get(
|
|
922
|
-
"path", KdlArg(value="", span=node.span, is_identifier=False)
|
|
923
|
-
).value
|
|
924
|
-
)
|
|
915
|
+
path = node.get_prop("path") or ""
|
|
925
916
|
json_def = ctx.json_defs.get(schema_name)
|
|
926
917
|
if json_def is None:
|
|
927
918
|
lint.error(
|