sqlmesh 0.196.4.dev2__py3-none-any.whl → 0.196.4.dev3__py3-none-any.whl
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.
- sqlmesh/_version.py +2 -2
- sqlmesh/core/linter/definition.py +5 -2
- sqlmesh/core/linter/rule.py +27 -2
- sqlmesh/core/linter/rules/builtin.py +25 -2
- sqlmesh/lsp/context.py +158 -0
- sqlmesh/lsp/main.py +95 -120
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/METADATA +1 -1
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/RECORD +12 -12
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/WHEEL +0 -0
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/licenses/LICENSE +0 -0
- {sqlmesh-0.196.4.dev2.dist-info → sqlmesh-0.196.4.dev3.dist-info}/top_level.txt +0 -0
sqlmesh/_version.py
CHANGED
|
@@ -17,5 +17,5 @@ __version__: str
|
|
|
17
17
|
__version_tuple__: VERSION_TUPLE
|
|
18
18
|
version_tuple: VERSION_TUPLE
|
|
19
19
|
|
|
20
|
-
__version__ = version = '0.196.4.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 196, 4, '
|
|
20
|
+
__version__ = version = '0.196.4.dev3'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 196, 4, 'dev3')
|
|
@@ -8,7 +8,7 @@ import operator as op
|
|
|
8
8
|
from collections.abc import Iterator, Iterable, Set, Mapping, Callable
|
|
9
9
|
from functools import reduce
|
|
10
10
|
from sqlmesh.core.model import Model
|
|
11
|
-
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
|
|
11
|
+
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range, Fix
|
|
12
12
|
from sqlmesh.core.console import LinterConsole, get_console
|
|
13
13
|
|
|
14
14
|
if t.TYPE_CHECKING:
|
|
@@ -75,6 +75,7 @@ class Linter:
|
|
|
75
75
|
model=model,
|
|
76
76
|
violation_type="error",
|
|
77
77
|
violation_range=violation.violation_range,
|
|
78
|
+
fixes=violation.fixes,
|
|
78
79
|
)
|
|
79
80
|
for violation in error_violations
|
|
80
81
|
] + [
|
|
@@ -84,6 +85,7 @@ class Linter:
|
|
|
84
85
|
model=model,
|
|
85
86
|
violation_type="warning",
|
|
86
87
|
violation_range=violation.violation_range,
|
|
88
|
+
fixes=violation.fixes,
|
|
87
89
|
)
|
|
88
90
|
for violation in warn_violations
|
|
89
91
|
]
|
|
@@ -152,7 +154,8 @@ class AnnotatedRuleViolation(RuleViolation):
|
|
|
152
154
|
model: Model,
|
|
153
155
|
violation_type: t.Literal["error", "warning"],
|
|
154
156
|
violation_range: t.Optional[Range] = None,
|
|
157
|
+
fixes: t.Optional[t.List[Fix]] = None,
|
|
155
158
|
) -> None:
|
|
156
|
-
super().__init__(rule, violation_msg, violation_range)
|
|
159
|
+
super().__init__(rule, violation_msg, violation_range, fixes)
|
|
157
160
|
self.model = model
|
|
158
161
|
self.violation_type = violation_type
|
sqlmesh/core/linter/rule.py
CHANGED
|
@@ -39,6 +39,22 @@ class Range:
|
|
|
39
39
|
end: Position
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class TextEdit:
|
|
44
|
+
"""A text edit to apply to a file."""
|
|
45
|
+
|
|
46
|
+
range: Range
|
|
47
|
+
new_text: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class Fix:
|
|
52
|
+
"""A fix that can be applied to resolve a rule violation."""
|
|
53
|
+
|
|
54
|
+
title: str
|
|
55
|
+
edits: t.List[TextEdit]
|
|
56
|
+
|
|
57
|
+
|
|
42
58
|
class _Rule(abc.ABCMeta):
|
|
43
59
|
def __new__(cls: Type[_Rule], clsname: str, bases: t.Tuple, attrs: t.Dict) -> _Rule:
|
|
44
60
|
attrs["name"] = clsname.lower()
|
|
@@ -66,10 +82,14 @@ class Rule(abc.ABC, metaclass=_Rule):
|
|
|
66
82
|
self,
|
|
67
83
|
violation_msg: t.Optional[str] = None,
|
|
68
84
|
violation_range: t.Optional[Range] = None,
|
|
85
|
+
fixes: t.Optional[t.List[Fix]] = None,
|
|
69
86
|
) -> RuleViolation:
|
|
70
87
|
"""Create a RuleViolation instance for this rule"""
|
|
71
88
|
return RuleViolation(
|
|
72
|
-
rule=self,
|
|
89
|
+
rule=self,
|
|
90
|
+
violation_msg=violation_msg or self.summary,
|
|
91
|
+
violation_range=violation_range,
|
|
92
|
+
fixes=fixes,
|
|
73
93
|
)
|
|
74
94
|
|
|
75
95
|
def get_definition_location(self) -> RuleLocation:
|
|
@@ -103,11 +123,16 @@ class Rule(abc.ABC, metaclass=_Rule):
|
|
|
103
123
|
|
|
104
124
|
class RuleViolation:
|
|
105
125
|
def __init__(
|
|
106
|
-
self,
|
|
126
|
+
self,
|
|
127
|
+
rule: Rule,
|
|
128
|
+
violation_msg: str,
|
|
129
|
+
violation_range: t.Optional[Range] = None,
|
|
130
|
+
fixes: t.Optional[t.List[Fix]] = None,
|
|
107
131
|
) -> None:
|
|
108
132
|
self.rule = rule
|
|
109
133
|
self.violation_msg = violation_msg
|
|
110
134
|
self.violation_range = violation_range
|
|
135
|
+
self.fixes = fixes or []
|
|
111
136
|
|
|
112
137
|
def __repr__(self) -> str:
|
|
113
138
|
return f"{self.rule.name}: {self.violation_msg}"
|
|
@@ -8,7 +8,7 @@ from sqlglot.expressions import Star
|
|
|
8
8
|
from sqlglot.helper import subclasses
|
|
9
9
|
|
|
10
10
|
from sqlmesh.core.linter.helpers import TokenPositionDetails
|
|
11
|
-
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
|
|
11
|
+
from sqlmesh.core.linter.rule import Rule, RuleViolation, Range, Fix, TextEdit
|
|
12
12
|
from sqlmesh.core.linter.definition import RuleSet
|
|
13
13
|
from sqlmesh.core.model import Model, SqlModel
|
|
14
14
|
|
|
@@ -22,7 +22,8 @@ class NoSelectStar(Rule):
|
|
|
22
22
|
return None
|
|
23
23
|
if model.query.is_star:
|
|
24
24
|
violation_range = self._get_range(model)
|
|
25
|
-
|
|
25
|
+
fixes = self._create_fixes(model, violation_range)
|
|
26
|
+
return self.violation(violation_range=violation_range, fixes=fixes)
|
|
26
27
|
return None
|
|
27
28
|
|
|
28
29
|
def _get_range(self, model: SqlModel) -> t.Optional[Range]:
|
|
@@ -37,6 +38,28 @@ class NoSelectStar(Rule):
|
|
|
37
38
|
|
|
38
39
|
return None
|
|
39
40
|
|
|
41
|
+
def _create_fixes(
|
|
42
|
+
self, model: SqlModel, violation_range: t.Optional[Range]
|
|
43
|
+
) -> t.Optional[t.List[Fix]]:
|
|
44
|
+
"""Create fixes for the SELECT * violation."""
|
|
45
|
+
if not violation_range:
|
|
46
|
+
return None
|
|
47
|
+
columns = model.columns_to_types
|
|
48
|
+
if not columns:
|
|
49
|
+
return None
|
|
50
|
+
new_text = ", ".join(columns.keys())
|
|
51
|
+
return [
|
|
52
|
+
Fix(
|
|
53
|
+
title="Replace SELECT * with explicit column list",
|
|
54
|
+
edits=[
|
|
55
|
+
TextEdit(
|
|
56
|
+
range=violation_range,
|
|
57
|
+
new_text=new_text,
|
|
58
|
+
)
|
|
59
|
+
],
|
|
60
|
+
)
|
|
61
|
+
]
|
|
62
|
+
|
|
40
63
|
|
|
41
64
|
class InvalidSelectStarExpansion(Rule):
|
|
42
65
|
def check_model(self, model: Model) -> t.Optional[RuleViolation]:
|
sqlmesh/lsp/context.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
import uuid
|
|
3
4
|
from sqlmesh.core.context import Context
|
|
4
5
|
import typing as t
|
|
5
6
|
|
|
@@ -8,6 +9,7 @@ from sqlmesh.core.linter.definition import AnnotatedRuleViolation
|
|
|
8
9
|
from sqlmesh.lsp.custom import ModelForRendering
|
|
9
10
|
from sqlmesh.lsp.custom import AllModelsResponse, RenderModelEntry
|
|
10
11
|
from sqlmesh.lsp.uri import URI
|
|
12
|
+
from lsprotocol import types
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
@dataclass
|
|
@@ -33,8 +35,14 @@ class LSPContext:
|
|
|
33
35
|
map: t.Dict[Path, t.Union[ModelTarget, AuditTarget]]
|
|
34
36
|
_render_cache: t.Dict[Path, t.List[RenderModelEntry]]
|
|
35
37
|
_lint_cache: t.Dict[Path, t.List[AnnotatedRuleViolation]]
|
|
38
|
+
_version_id: str
|
|
39
|
+
"""
|
|
40
|
+
This is a version ID for the context. It is used to track changes to the context. It can be used to
|
|
41
|
+
return a version number to the LSP client.
|
|
42
|
+
"""
|
|
36
43
|
|
|
37
44
|
def __init__(self, context: Context) -> None:
|
|
45
|
+
self._version_id = str(uuid.uuid4())
|
|
38
46
|
self.context = context
|
|
39
47
|
self._render_cache = {}
|
|
40
48
|
self._lint_cache = {}
|
|
@@ -62,6 +70,11 @@ class LSPContext:
|
|
|
62
70
|
**audit_map,
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
@property
|
|
74
|
+
def version_id(self) -> str:
|
|
75
|
+
"""Get the version ID for the context."""
|
|
76
|
+
return self._version_id
|
|
77
|
+
|
|
65
78
|
def render_model(self, uri: URI) -> t.List[RenderModelEntry]:
|
|
66
79
|
"""Get rendered models for a file, using cache when available.
|
|
67
80
|
|
|
@@ -150,6 +163,86 @@ class LSPContext:
|
|
|
150
163
|
self._lint_cache[path] = diagnostics
|
|
151
164
|
return diagnostics
|
|
152
165
|
|
|
166
|
+
def get_code_actions(
|
|
167
|
+
self, uri: URI, params: types.CodeActionParams
|
|
168
|
+
) -> t.Optional[t.List[t.Union[types.Command, types.CodeAction]]]:
|
|
169
|
+
"""Get code actions for a file."""
|
|
170
|
+
|
|
171
|
+
# Get the violations (which contain the fixes)
|
|
172
|
+
violations = self.lint_model(uri)
|
|
173
|
+
|
|
174
|
+
# Convert violations to a map for quick lookup
|
|
175
|
+
# Use a hashable representation of Range as the key
|
|
176
|
+
violation_map: t.Dict[
|
|
177
|
+
t.Tuple[str, t.Tuple[int, int, int, int]], AnnotatedRuleViolation
|
|
178
|
+
] = {}
|
|
179
|
+
for violation in violations:
|
|
180
|
+
if violation.violation_range:
|
|
181
|
+
lsp_diagnostic = self.diagnostic_to_lsp_diagnostic(violation)
|
|
182
|
+
if lsp_diagnostic:
|
|
183
|
+
# Create a hashable key from the diagnostic message and range
|
|
184
|
+
key = (
|
|
185
|
+
lsp_diagnostic.message,
|
|
186
|
+
(
|
|
187
|
+
lsp_diagnostic.range.start.line,
|
|
188
|
+
lsp_diagnostic.range.start.character,
|
|
189
|
+
lsp_diagnostic.range.end.line,
|
|
190
|
+
lsp_diagnostic.range.end.character,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
violation_map[key] = violation
|
|
194
|
+
|
|
195
|
+
# Get diagnostics in the requested range
|
|
196
|
+
diagnostics = params.context.diagnostics if params.context else []
|
|
197
|
+
|
|
198
|
+
code_actions: t.List[t.Union[types.Command, types.CodeAction]] = []
|
|
199
|
+
|
|
200
|
+
for diagnostic in diagnostics:
|
|
201
|
+
# Find the corresponding violation
|
|
202
|
+
key = (
|
|
203
|
+
diagnostic.message,
|
|
204
|
+
(
|
|
205
|
+
diagnostic.range.start.line,
|
|
206
|
+
diagnostic.range.start.character,
|
|
207
|
+
diagnostic.range.end.line,
|
|
208
|
+
diagnostic.range.end.character,
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
found_violation = violation_map.get(key)
|
|
212
|
+
|
|
213
|
+
if found_violation is not None and found_violation.fixes:
|
|
214
|
+
# Create code actions for each fix
|
|
215
|
+
for fix in found_violation.fixes:
|
|
216
|
+
# Convert our Fix to LSP TextEdits
|
|
217
|
+
text_edits = []
|
|
218
|
+
for edit in fix.edits:
|
|
219
|
+
text_edits.append(
|
|
220
|
+
types.TextEdit(
|
|
221
|
+
range=types.Range(
|
|
222
|
+
start=types.Position(
|
|
223
|
+
line=edit.range.start.line,
|
|
224
|
+
character=edit.range.start.character,
|
|
225
|
+
),
|
|
226
|
+
end=types.Position(
|
|
227
|
+
line=edit.range.end.line,
|
|
228
|
+
character=edit.range.end.character,
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
new_text=edit.new_text,
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Create the code action
|
|
236
|
+
code_action = types.CodeAction(
|
|
237
|
+
title=fix.title,
|
|
238
|
+
kind=types.CodeActionKind.QuickFix,
|
|
239
|
+
diagnostics=[diagnostic],
|
|
240
|
+
edit=types.WorkspaceEdit(changes={params.text_document.uri: text_edits}),
|
|
241
|
+
)
|
|
242
|
+
code_actions.append(code_action)
|
|
243
|
+
|
|
244
|
+
return code_actions if code_actions else None
|
|
245
|
+
|
|
153
246
|
def list_of_models_for_rendering(self) -> t.List[ModelForRendering]:
|
|
154
247
|
"""Get a list of models for rendering.
|
|
155
248
|
|
|
@@ -186,3 +279,68 @@ class LSPContext:
|
|
|
186
279
|
from sqlmesh.lsp.completions import get_sql_completions
|
|
187
280
|
|
|
188
281
|
return get_sql_completions(self, uri, file_content)
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def diagnostics_to_lsp_diagnostics(
|
|
285
|
+
diagnostics: t.List[AnnotatedRuleViolation],
|
|
286
|
+
) -> t.List[types.Diagnostic]:
|
|
287
|
+
"""
|
|
288
|
+
Converts a list of AnnotatedRuleViolations to a list of LSP diagnostics. It will remove duplicates based on the message and range.
|
|
289
|
+
"""
|
|
290
|
+
lsp_diagnostics = {}
|
|
291
|
+
for diagnostic in diagnostics:
|
|
292
|
+
lsp_diagnostic = LSPContext.diagnostic_to_lsp_diagnostic(diagnostic)
|
|
293
|
+
if lsp_diagnostic is not None:
|
|
294
|
+
# Create a unique key combining message and range
|
|
295
|
+
diagnostic_key = (
|
|
296
|
+
lsp_diagnostic.message,
|
|
297
|
+
lsp_diagnostic.range.start.line,
|
|
298
|
+
lsp_diagnostic.range.start.character,
|
|
299
|
+
lsp_diagnostic.range.end.line,
|
|
300
|
+
lsp_diagnostic.range.end.character,
|
|
301
|
+
)
|
|
302
|
+
if diagnostic_key not in lsp_diagnostics:
|
|
303
|
+
lsp_diagnostics[diagnostic_key] = lsp_diagnostic
|
|
304
|
+
return list(lsp_diagnostics.values())
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def diagnostic_to_lsp_diagnostic(
|
|
308
|
+
diagnostic: AnnotatedRuleViolation,
|
|
309
|
+
) -> t.Optional[types.Diagnostic]:
|
|
310
|
+
if diagnostic.model._path is None:
|
|
311
|
+
return None
|
|
312
|
+
if not diagnostic.violation_range:
|
|
313
|
+
with open(diagnostic.model._path, "r", encoding="utf-8") as file:
|
|
314
|
+
lines = file.readlines()
|
|
315
|
+
diagnostic_range = types.Range(
|
|
316
|
+
start=types.Position(line=0, character=0),
|
|
317
|
+
end=types.Position(line=len(lines) - 1, character=len(lines[-1])),
|
|
318
|
+
)
|
|
319
|
+
else:
|
|
320
|
+
diagnostic_range = types.Range(
|
|
321
|
+
start=types.Position(
|
|
322
|
+
line=diagnostic.violation_range.start.line,
|
|
323
|
+
character=diagnostic.violation_range.start.character,
|
|
324
|
+
),
|
|
325
|
+
end=types.Position(
|
|
326
|
+
line=diagnostic.violation_range.end.line,
|
|
327
|
+
character=diagnostic.violation_range.end.character,
|
|
328
|
+
),
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Get rule definition location for diagnostics link
|
|
332
|
+
rule_location = diagnostic.rule.get_definition_location()
|
|
333
|
+
rule_uri_wihout_extension = URI.from_path(rule_location.file_path)
|
|
334
|
+
rule_uri = f"{rule_uri_wihout_extension.value}#L{rule_location.start_line}"
|
|
335
|
+
|
|
336
|
+
# Use URI format to create a link for "related information"
|
|
337
|
+
return types.Diagnostic(
|
|
338
|
+
range=diagnostic_range,
|
|
339
|
+
message=diagnostic.violation_msg,
|
|
340
|
+
severity=types.DiagnosticSeverity.Error
|
|
341
|
+
if diagnostic.violation_type == "error"
|
|
342
|
+
else types.DiagnosticSeverity.Warning,
|
|
343
|
+
source="sqlmesh",
|
|
344
|
+
code=diagnostic.rule.name,
|
|
345
|
+
code_description=types.CodeDescription(href=rule_uri),
|
|
346
|
+
)
|
sqlmesh/lsp/main.py
CHANGED
|
@@ -8,11 +8,13 @@ from pathlib import Path
|
|
|
8
8
|
import urllib.parse
|
|
9
9
|
|
|
10
10
|
from lsprotocol import types
|
|
11
|
+
from lsprotocol.types import (
|
|
12
|
+
WorkspaceDiagnosticRefreshRequest,
|
|
13
|
+
WorkspaceInlayHintRefreshRequest,
|
|
14
|
+
)
|
|
11
15
|
from pygls.server import LanguageServer
|
|
12
|
-
|
|
13
16
|
from sqlmesh._version import __version__
|
|
14
17
|
from sqlmesh.core.context import Context
|
|
15
|
-
from sqlmesh.core.linter.definition import AnnotatedRuleViolation
|
|
16
18
|
from sqlmesh.lsp.api import (
|
|
17
19
|
API_FEATURE,
|
|
18
20
|
ApiRequest,
|
|
@@ -216,6 +218,79 @@ class SQLMeshLanguageServer:
|
|
|
216
218
|
]
|
|
217
219
|
)
|
|
218
220
|
|
|
221
|
+
def _reload_context_and_publish_diagnostics(
|
|
222
|
+
self, ls: LanguageServer, uri: URI, document_uri: str
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Helper method to reload context and publish diagnostics."""
|
|
225
|
+
if isinstance(self.context_state, NoContext):
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
if isinstance(self.context_state, ContextFailed):
|
|
229
|
+
if self.context_state.context:
|
|
230
|
+
try:
|
|
231
|
+
self.context_state.context.load()
|
|
232
|
+
# Creating a new LSPContext will naturally create fresh caches
|
|
233
|
+
self.context_state = ContextLoaded(
|
|
234
|
+
lsp_context=LSPContext(self.context_state.context)
|
|
235
|
+
)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
ls.log_trace(f"Error loading context: {e}")
|
|
238
|
+
if not isinstance(self.context_state, ContextFailed):
|
|
239
|
+
raise Exception("Context state should be failed")
|
|
240
|
+
self.context_state = ContextFailed(
|
|
241
|
+
error_message=str(e), context=self.context_state.context
|
|
242
|
+
)
|
|
243
|
+
return
|
|
244
|
+
else:
|
|
245
|
+
# If there's no context, try to create one from scratch
|
|
246
|
+
try:
|
|
247
|
+
self._ensure_context_for_document(uri)
|
|
248
|
+
# If successful, context_state will be ContextLoaded
|
|
249
|
+
if isinstance(self.context_state, ContextLoaded):
|
|
250
|
+
ls.show_message(
|
|
251
|
+
"Successfully loaded SQLMesh context",
|
|
252
|
+
types.MessageType.Info,
|
|
253
|
+
)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
ls.log_trace(f"Still cannot load context: {e}")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
# Reload the context if it was successfully loaded
|
|
259
|
+
try:
|
|
260
|
+
context = self.context_state.lsp_context.context
|
|
261
|
+
context.load()
|
|
262
|
+
# Create new LSPContext which will have fresh, empty caches
|
|
263
|
+
self.context_state = ContextLoaded(lsp_context=LSPContext(context))
|
|
264
|
+
except Exception as e:
|
|
265
|
+
ls.log_trace(f"Error loading context: {e}")
|
|
266
|
+
self.context_state = ContextFailed(
|
|
267
|
+
error_message=str(e), context=self.context_state.lsp_context.context
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
# Send a workspace diagnostic refresh request to the client. This is used to notify the client that the diagnostics have changed.
|
|
272
|
+
ls.lsp.send_request(
|
|
273
|
+
types.WORKSPACE_DIAGNOSTIC_REFRESH,
|
|
274
|
+
WorkspaceDiagnosticRefreshRequest(
|
|
275
|
+
id=self.context_state.lsp_context.version_id,
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
ls.lsp.send_request(
|
|
280
|
+
types.WORKSPACE_INLAY_HINT_REFRESH,
|
|
281
|
+
WorkspaceInlayHintRefreshRequest(
|
|
282
|
+
id=self.context_state.lsp_context.version_id,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Only publish diagnostics if client doesn't support pull diagnostics
|
|
287
|
+
if not self.client_supports_pull_diagnostics:
|
|
288
|
+
diagnostics = self.context_state.lsp_context.lint_model(uri)
|
|
289
|
+
ls.publish_diagnostics(
|
|
290
|
+
document_uri,
|
|
291
|
+
LSPContext.diagnostics_to_lsp_diagnostics(diagnostics),
|
|
292
|
+
)
|
|
293
|
+
|
|
219
294
|
def _register_features(self) -> None:
|
|
220
295
|
"""Register LSP features on the internal LanguageServer instance."""
|
|
221
296
|
for name, method in self._supported_custom_methods.items():
|
|
@@ -278,63 +353,13 @@ class SQLMeshLanguageServer:
|
|
|
278
353
|
diagnostics = context.lint_model(uri)
|
|
279
354
|
ls.publish_diagnostics(
|
|
280
355
|
params.text_document.uri,
|
|
281
|
-
|
|
356
|
+
LSPContext.diagnostics_to_lsp_diagnostics(diagnostics),
|
|
282
357
|
)
|
|
283
358
|
|
|
284
359
|
@self.server.feature(types.TEXT_DOCUMENT_DID_SAVE)
|
|
285
360
|
def did_save(ls: LanguageServer, params: types.DidSaveTextDocumentParams) -> None:
|
|
286
361
|
uri = URI(params.text_document.uri)
|
|
287
|
-
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
if isinstance(self.context_state, ContextFailed):
|
|
291
|
-
if self.context_state.context:
|
|
292
|
-
try:
|
|
293
|
-
self.context_state.context.load()
|
|
294
|
-
self.context_state = ContextLoaded(
|
|
295
|
-
lsp_context=LSPContext(self.context_state.context)
|
|
296
|
-
)
|
|
297
|
-
except Exception as e:
|
|
298
|
-
ls.log_trace(f"Error loading context: {e}")
|
|
299
|
-
if not isinstance(self.context_state, ContextFailed):
|
|
300
|
-
raise Exception("Context state should be failed")
|
|
301
|
-
self.context_state = ContextFailed(
|
|
302
|
-
error_message=str(e), context=self.context_state.context
|
|
303
|
-
)
|
|
304
|
-
return
|
|
305
|
-
else:
|
|
306
|
-
# If there's no context, try to create one from scratch
|
|
307
|
-
try:
|
|
308
|
-
self._ensure_context_for_document(uri)
|
|
309
|
-
# If successful, context_state will be ContextLoaded
|
|
310
|
-
if isinstance(self.context_state, ContextLoaded):
|
|
311
|
-
ls.show_message(
|
|
312
|
-
"Successfully loaded SQLMesh context",
|
|
313
|
-
types.MessageType.Info,
|
|
314
|
-
)
|
|
315
|
-
except Exception as e:
|
|
316
|
-
ls.log_trace(f"Still cannot load context: {e}")
|
|
317
|
-
return
|
|
318
|
-
|
|
319
|
-
# Reload the context if was successfully
|
|
320
|
-
try:
|
|
321
|
-
context = self.context_state.lsp_context.context
|
|
322
|
-
context.load()
|
|
323
|
-
self.context_state = ContextLoaded(lsp_context=LSPContext(context))
|
|
324
|
-
except Exception as e:
|
|
325
|
-
ls.log_trace(f"Error loading context: {e}")
|
|
326
|
-
self.context_state = ContextFailed(
|
|
327
|
-
error_message=str(e), context=self.context_state.lsp_context.context
|
|
328
|
-
)
|
|
329
|
-
return
|
|
330
|
-
|
|
331
|
-
# Only publish diagnostics if client doesn't support pull diagnostics
|
|
332
|
-
if not self.client_supports_pull_diagnostics:
|
|
333
|
-
diagnostics = self.context_state.lsp_context.lint_model(uri)
|
|
334
|
-
ls.publish_diagnostics(
|
|
335
|
-
params.text_document.uri,
|
|
336
|
-
SQLMeshLanguageServer._diagnostics_to_lsp_diagnostics(diagnostics),
|
|
337
|
-
)
|
|
362
|
+
self._reload_context_and_publish_diagnostics(ls, uri, params.text_document.uri)
|
|
338
363
|
|
|
339
364
|
@self.server.feature(types.TEXT_DOCUMENT_FORMATTING)
|
|
340
365
|
def formatting(
|
|
@@ -630,6 +655,21 @@ class SQLMeshLanguageServer:
|
|
|
630
655
|
)
|
|
631
656
|
return types.WorkspaceDiagnosticReport(items=[])
|
|
632
657
|
|
|
658
|
+
@self.server.feature(types.TEXT_DOCUMENT_CODE_ACTION)
|
|
659
|
+
def code_action(
|
|
660
|
+
ls: LanguageServer, params: types.CodeActionParams
|
|
661
|
+
) -> t.Optional[t.List[t.Union[types.Command, types.CodeAction]]]:
|
|
662
|
+
try:
|
|
663
|
+
ls.log_trace(f"Codeactionrequest: {params}")
|
|
664
|
+
uri = URI(params.text_document.uri)
|
|
665
|
+
context = self._context_get_or_load(uri)
|
|
666
|
+
code_actions = context.get_code_actions(uri, params)
|
|
667
|
+
return code_actions
|
|
668
|
+
|
|
669
|
+
except Exception as e:
|
|
670
|
+
ls.log_trace(f"Error getting code actions: {e}")
|
|
671
|
+
return None
|
|
672
|
+
|
|
633
673
|
@self.server.feature(
|
|
634
674
|
types.TEXT_DOCUMENT_COMPLETION,
|
|
635
675
|
types.CompletionOptions(trigger_characters=["@"]), # advertise "@" for macros
|
|
@@ -718,7 +758,7 @@ class SQLMeshLanguageServer:
|
|
|
718
758
|
try:
|
|
719
759
|
context = self._context_get_or_load(uri)
|
|
720
760
|
diagnostics = context.lint_model(uri)
|
|
721
|
-
return
|
|
761
|
+
return LSPContext.diagnostics_to_lsp_diagnostics(diagnostics), 0
|
|
722
762
|
except Exception:
|
|
723
763
|
return [], 0
|
|
724
764
|
|
|
@@ -829,71 +869,6 @@ class SQLMeshLanguageServer:
|
|
|
829
869
|
self.context_state = ContextFailed(error_message=str(e), context=context)
|
|
830
870
|
return None
|
|
831
871
|
|
|
832
|
-
@staticmethod
|
|
833
|
-
def _diagnostic_to_lsp_diagnostic(
|
|
834
|
-
diagnostic: AnnotatedRuleViolation,
|
|
835
|
-
) -> t.Optional[types.Diagnostic]:
|
|
836
|
-
if diagnostic.model._path is None:
|
|
837
|
-
return None
|
|
838
|
-
if not diagnostic.violation_range:
|
|
839
|
-
with open(diagnostic.model._path, "r", encoding="utf-8") as file:
|
|
840
|
-
lines = file.readlines()
|
|
841
|
-
range = types.Range(
|
|
842
|
-
start=types.Position(line=0, character=0),
|
|
843
|
-
end=types.Position(line=len(lines) - 1, character=len(lines[-1])),
|
|
844
|
-
)
|
|
845
|
-
else:
|
|
846
|
-
range = types.Range(
|
|
847
|
-
start=types.Position(
|
|
848
|
-
line=diagnostic.violation_range.start.line,
|
|
849
|
-
character=diagnostic.violation_range.start.character,
|
|
850
|
-
),
|
|
851
|
-
end=types.Position(
|
|
852
|
-
line=diagnostic.violation_range.end.line,
|
|
853
|
-
character=diagnostic.violation_range.end.character,
|
|
854
|
-
),
|
|
855
|
-
)
|
|
856
|
-
|
|
857
|
-
# Get rule definition location for diagnostics link
|
|
858
|
-
rule_location = diagnostic.rule.get_definition_location()
|
|
859
|
-
rule_uri_wihout_extension = URI.from_path(rule_location.file_path)
|
|
860
|
-
rule_uri = f"{rule_uri_wihout_extension.value}#L{rule_location.start_line}"
|
|
861
|
-
|
|
862
|
-
# Use URI format to create a link for "related information"
|
|
863
|
-
return types.Diagnostic(
|
|
864
|
-
range=range,
|
|
865
|
-
message=diagnostic.violation_msg,
|
|
866
|
-
severity=types.DiagnosticSeverity.Error
|
|
867
|
-
if diagnostic.violation_type == "error"
|
|
868
|
-
else types.DiagnosticSeverity.Warning,
|
|
869
|
-
source="sqlmesh",
|
|
870
|
-
code=diagnostic.rule.name,
|
|
871
|
-
code_description=types.CodeDescription(href=rule_uri),
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
@staticmethod
|
|
875
|
-
def _diagnostics_to_lsp_diagnostics(
|
|
876
|
-
diagnostics: t.List[AnnotatedRuleViolation],
|
|
877
|
-
) -> t.List[types.Diagnostic]:
|
|
878
|
-
"""
|
|
879
|
-
Converts a list of AnnotatedRuleViolations to a list of LSP diagnostics. It will remove duplicates based on the message and range.
|
|
880
|
-
"""
|
|
881
|
-
lsp_diagnostics = {}
|
|
882
|
-
for diagnostic in diagnostics:
|
|
883
|
-
lsp_diagnostic = SQLMeshLanguageServer._diagnostic_to_lsp_diagnostic(diagnostic)
|
|
884
|
-
if lsp_diagnostic is not None:
|
|
885
|
-
# Create a unique key combining message and range
|
|
886
|
-
diagnostic_key = (
|
|
887
|
-
lsp_diagnostic.message,
|
|
888
|
-
lsp_diagnostic.range.start.line,
|
|
889
|
-
lsp_diagnostic.range.start.character,
|
|
890
|
-
lsp_diagnostic.range.end.line,
|
|
891
|
-
lsp_diagnostic.range.end.character,
|
|
892
|
-
)
|
|
893
|
-
if diagnostic_key not in lsp_diagnostics:
|
|
894
|
-
lsp_diagnostics[diagnostic_key] = lsp_diagnostic
|
|
895
|
-
return list(lsp_diagnostics.values())
|
|
896
|
-
|
|
897
872
|
@staticmethod
|
|
898
873
|
def _uri_to_path(uri: str) -> Path:
|
|
899
874
|
"""Convert a URI to a path."""
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
|
|
2
|
-
sqlmesh/_version.py,sha256=
|
|
2
|
+
sqlmesh/_version.py,sha256=qpC7oSM5ytmbweRolzwz-C_TfdZEg-XTlrkpntxGzVw,528
|
|
3
3
|
sqlmesh/magics.py,sha256=nMfwSW5rIkdnyO9MXYm9u24hNl6dqgAWybC-MnRcNnE,41451
|
|
4
4
|
sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -76,11 +76,11 @@ sqlmesh/core/engine_adapter/snowflake.py,sha256=IEEzepxNpfqBIMe4ziz2k8fjOcJPeQPZ
|
|
|
76
76
|
sqlmesh/core/engine_adapter/spark.py,sha256=sYkVOPS0X43UPbI0z7Hh_EpKlI36qkOcr4NNFOTQlLs,21045
|
|
77
77
|
sqlmesh/core/engine_adapter/trino.py,sha256=7WdLFlT0AyD5NF0EDP7GqX0SHF8sTm7cafBZxk84cmY,15798
|
|
78
78
|
sqlmesh/core/linter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
79
|
-
sqlmesh/core/linter/definition.py,sha256=
|
|
79
|
+
sqlmesh/core/linter/definition.py,sha256=pXDxl8TxTGNuVc1B5ywYmaH7dDjsoMRvCtLCK_co-ek,5567
|
|
80
80
|
sqlmesh/core/linter/helpers.py,sha256=VF4hsdzGZ8srpbtCjdRN_Hd8S_er5KTAaLXLde27Yxo,4032
|
|
81
|
-
sqlmesh/core/linter/rule.py,sha256=
|
|
81
|
+
sqlmesh/core/linter/rule.py,sha256=FlsP0PN4dmke7sJ2oD8ZZo3tWw8i87Q7CvbdNOOy3N8,3648
|
|
82
82
|
sqlmesh/core/linter/rules/__init__.py,sha256=gevzfb67vFqckTCoVAe_TBGf6hQ-YtE1_YuGuXyh1L0,77
|
|
83
|
-
sqlmesh/core/linter/rules/builtin.py,sha256=
|
|
83
|
+
sqlmesh/core/linter/rules/builtin.py,sha256=51pv76heU6dlTHBMvL3L1x1WXzbCAsFBJGRQ8zjk-OU,3452
|
|
84
84
|
sqlmesh/core/metric/__init__.py,sha256=H1HmoD5IwN4YWe9iJXyueLYNmTQFZwok5nSWNJcZIBQ,237
|
|
85
85
|
sqlmesh/core/metric/definition.py,sha256=Yd5aVgsZCDPJ43aGP7WqtzZOuuSUtB8uJGVA6Jw9x9M,7201
|
|
86
86
|
sqlmesh/core/metric/rewriter.py,sha256=GiSTHfn2kinqCfNPYgZPRk93JFLzVaaejHtHDQ0yXZI,7326
|
|
@@ -167,12 +167,12 @@ sqlmesh/integrations/github/cicd/config.py,sha256=tRhMZaLf1p6AWXvi67n85Q-lptJNGt
|
|
|
167
167
|
sqlmesh/integrations/github/cicd/controller.py,sha256=QCs51Ag52Eu-2C5B2ivJ3Jgh7yThKj0eBqILGCHqizk,44436
|
|
168
168
|
sqlmesh/lsp/api.py,sha256=_lA_buSPZFIoLJuINbS53LbNOOaHp3orb0uFTE3aCqI,2328
|
|
169
169
|
sqlmesh/lsp/completions.py,sha256=Nt1XpFL_-QAIRM8BBBC6Ia6YG0hMZriiNJra7nGfD4Q,6676
|
|
170
|
-
sqlmesh/lsp/context.py,sha256=
|
|
170
|
+
sqlmesh/lsp/context.py,sha256=2aD_lH-GqAwCjTFHfCavH2fZz2T4IXQZVS7LDMAa2X4,12815
|
|
171
171
|
sqlmesh/lsp/custom.py,sha256=IJ70li8d5RbOFDrjHfsI2UNOiOp-WB22lrgpdJ7lVSg,2942
|
|
172
172
|
sqlmesh/lsp/description.py,sha256=PjguwyVw5YeYSgT0JMhkf5U1Jry-sDPLvQ2caIR9JPQ,856
|
|
173
173
|
sqlmesh/lsp/helpers.py,sha256=EFc1u3-b7kSv5-tNwmKUDxId72RCLDBnN2lLTgRSzzQ,1020
|
|
174
174
|
sqlmesh/lsp/hints.py,sha256=j-blccBUil94yRoDjWfkSM0HqMmsKD6hP2TaWdWWVk4,3734
|
|
175
|
-
sqlmesh/lsp/main.py,sha256=
|
|
175
|
+
sqlmesh/lsp/main.py,sha256=TRuegZaC04cPDThtmz_zL6YqBpQz342brv1luIbDTZ8,38186
|
|
176
176
|
sqlmesh/lsp/reference.py,sha256=NyfpK12TouDuy5-G7KOhvifkJItMfrakKwnMoU6SiVk,32088
|
|
177
177
|
sqlmesh/lsp/rename.py,sha256=399v1vyVRHHxvbCHVcuS4Pl8K2N-Z60JdZ6e1znOI4s,4397
|
|
178
178
|
sqlmesh/lsp/uri.py,sha256=mvSLbZGmMTHPpliThOtVWiFcHnfJzp1w39q9l-xD2SI,802
|
|
@@ -283,7 +283,7 @@ sqlmesh/utils/pydantic.py,sha256=I898jAFqa3qU6lG2uPpRebVFncOUo8ZrLWM_R3K3DHU,116
|
|
|
283
283
|
sqlmesh/utils/rich.py,sha256=BpB47G0-JcU41LkGkeGKroexj4NOV5fox4-GOIXPgIc,3545
|
|
284
284
|
sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
|
|
285
285
|
sqlmesh/utils/yaml.py,sha256=hvo5lmybYLcIx4p6CQwufS8X4MbwS60lEBnCmYJCXmU,2336
|
|
286
|
-
sqlmesh-0.196.4.
|
|
286
|
+
sqlmesh-0.196.4.dev3.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
|
|
287
287
|
web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
288
288
|
web/client/dist/index.html,sha256=drXwzSCEVDTMfaIwzJYjnT8Xk4c-Y4wNJz-CflUa_vY,5472
|
|
289
289
|
web/client/dist/assets/Audits-DrNQaTQA.js,sha256=E8JF632bsRUm6LGtgXlyHrJOYQe5VW2CgRZPaZNKvgg,1041
|
|
@@ -401,8 +401,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
|
|
|
401
401
|
web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
|
|
402
402
|
web/server/api/endpoints/plan.py,sha256=OYuQ_4dgbhG9ibTcX1a8mvYgH3ANxw9Fhbb4wg6QWqc,9222
|
|
403
403
|
web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
|
|
404
|
-
sqlmesh-0.196.4.
|
|
405
|
-
sqlmesh-0.196.4.
|
|
406
|
-
sqlmesh-0.196.4.
|
|
407
|
-
sqlmesh-0.196.4.
|
|
408
|
-
sqlmesh-0.196.4.
|
|
404
|
+
sqlmesh-0.196.4.dev3.dist-info/METADATA,sha256=_UapKkAgpnTTyUueOzzOCZW_7P1Zip9IxV97CUrQyoo,26527
|
|
405
|
+
sqlmesh-0.196.4.dev3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
406
|
+
sqlmesh-0.196.4.dev3.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
|
|
407
|
+
sqlmesh-0.196.4.dev3.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
|
|
408
|
+
sqlmesh-0.196.4.dev3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|