sqlmesh 0.189.2.dev5__py3-none-any.whl → 0.189.3.dev1__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 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.189.2.dev5'
21
- __version_tuple__ = version_tuple = (0, 189, 2, 'dev5')
20
+ __version__ = version = '0.189.3.dev1'
21
+ __version_tuple__ = version_tuple = (0, 189, 3, 'dev1')
@@ -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
11
+ from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
12
12
  from sqlmesh.core.console import LinterConsole, get_console
13
13
 
14
14
  if t.TYPE_CHECKING:
@@ -74,6 +74,7 @@ class Linter:
74
74
  violation_msg=violation.violation_msg,
75
75
  model=model,
76
76
  violation_type="error",
77
+ violation_range=violation.violation_range,
77
78
  )
78
79
  for violation in error_violations
79
80
  ] + [
@@ -82,6 +83,7 @@ class Linter:
82
83
  violation_msg=violation.violation_msg,
83
84
  model=model,
84
85
  violation_type="warning",
86
+ violation_range=violation.violation_range,
85
87
  )
86
88
  for violation in warn_violations
87
89
  ]
@@ -149,7 +151,8 @@ class AnnotatedRuleViolation(RuleViolation):
149
151
  violation_msg: str,
150
152
  model: Model,
151
153
  violation_type: t.Literal["error", "warning"],
154
+ violation_range: t.Optional[Range] = None,
152
155
  ) -> None:
153
- super().__init__(rule, violation_msg)
156
+ super().__init__(rule, violation_msg, violation_range)
154
157
  self.model = model
155
158
  self.violation_type = violation_type
@@ -0,0 +1,115 @@
1
+ from pathlib import Path
2
+
3
+ from sqlmesh.core.linter.rule import Position, Range
4
+ from sqlmesh.utils.pydantic import PydanticModel
5
+ import typing as t
6
+
7
+
8
+ class TokenPositionDetails(PydanticModel):
9
+ """
10
+ Details about a token's position in the source code in the structure provided by SQLGlot.
11
+
12
+ Attributes:
13
+ line (int): The line that the token ends on.
14
+ col (int): The column that the token ends on.
15
+ start (int): The start index of the token.
16
+ end (int): The ending index of the token.
17
+ """
18
+
19
+ line: int
20
+ col: int
21
+ start: int
22
+ end: int
23
+
24
+ @staticmethod
25
+ def from_meta(meta: t.Dict[str, int]) -> "TokenPositionDetails":
26
+ return TokenPositionDetails(
27
+ line=meta["line"],
28
+ col=meta["col"],
29
+ start=meta["start"],
30
+ end=meta["end"],
31
+ )
32
+
33
+ def to_range(self, read_file: t.Optional[t.List[str]]) -> Range:
34
+ """
35
+ Convert a TokenPositionDetails object to a Range object.
36
+
37
+ In the circumstances where the token's start and end positions are the same,
38
+ there is no need for a read_file parameter, as the range can be derived from the token's
39
+ line and column. This is an optimization to avoid unnecessary file reads and should
40
+ only be used when the token represents a single character or position in the file.
41
+
42
+ If the token's start and end positions are different, the read_file parameter is required.
43
+
44
+ :param read_file: List of lines from the file. Optional
45
+ :return: A Range object representing the token's position
46
+ """
47
+ if self.start == self.end:
48
+ # If the start and end positions are the same, we can create a range directly
49
+ return Range(
50
+ start=Position(line=self.line - 1, character=self.col - 1),
51
+ end=Position(line=self.line - 1, character=self.col),
52
+ )
53
+
54
+ if read_file is None:
55
+ raise ValueError("read_file must be provided when start and end positions differ.")
56
+
57
+ # Convert from 1-indexed to 0-indexed for line only
58
+ end_line_0 = self.line - 1
59
+ end_col_0 = self.col
60
+
61
+ # Find the start line and column by counting backwards from the end position
62
+ start_pos = self.start
63
+ end_pos = self.end
64
+
65
+ # Initialize with the end position
66
+ start_line_0 = end_line_0
67
+ start_col_0 = end_col_0 - (end_pos - start_pos + 1)
68
+
69
+ # If start_col_0 is negative, we need to go back to previous lines
70
+ while start_col_0 < 0 and start_line_0 > 0:
71
+ start_line_0 -= 1
72
+ start_col_0 += len(read_file[start_line_0])
73
+ # Account for newline character
74
+ if start_col_0 >= 0:
75
+ break
76
+ start_col_0 += 1 # For the newline character
77
+
78
+ # Ensure we don't have negative values
79
+ start_col_0 = max(0, start_col_0)
80
+ return Range(
81
+ start=Position(line=start_line_0, character=start_col_0),
82
+ end=Position(line=end_line_0, character=end_col_0),
83
+ )
84
+
85
+
86
+ def read_range_from_file(file: Path, text_range: Range) -> str:
87
+ """
88
+ Read the file and return the content within the specified range.
89
+
90
+ Args:
91
+ file: Path to the file to read
92
+ text_range: The range of text to extract
93
+
94
+ Returns:
95
+ The content within the specified range
96
+ """
97
+ with file.open("r", encoding="utf-8") as f:
98
+ lines = f.readlines()
99
+
100
+ # Ensure the range is within bounds
101
+ start_line = max(0, text_range.start.line)
102
+ end_line = min(len(lines), text_range.end.line + 1)
103
+
104
+ if start_line >= end_line:
105
+ return ""
106
+
107
+ # Extract the relevant portions of each line
108
+ result = []
109
+ for i in range(start_line, end_line):
110
+ line = lines[i]
111
+ start_char = text_range.start.character if i == text_range.start.line else 0
112
+ end_char = text_range.end.character if i == text_range.end.line else len(line)
113
+ result.append(line[start_char:end_char])
114
+
115
+ return "".join(result)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
+ from dataclasses import dataclass
4
5
 
5
6
  from sqlmesh.core.model import Model
6
7
 
@@ -22,6 +23,22 @@ class RuleLocation(PydanticModel):
22
23
  start_line: t.Optional[int] = None
23
24
 
24
25
 
26
+ @dataclass(frozen=True)
27
+ class Position:
28
+ """The position of a rule violation in a file, the position follows the LSP standard."""
29
+
30
+ line: int
31
+ character: int
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class Range:
36
+ """The range of a rule violation in a file. The range follows the LSP standard."""
37
+
38
+ start: Position
39
+ end: Position
40
+
41
+
25
42
  class _Rule(abc.ABCMeta):
26
43
  def __new__(cls: Type[_Rule], clsname: str, bases: t.Tuple, attrs: t.Dict) -> _Rule:
27
44
  attrs["name"] = clsname.lower()
@@ -45,9 +62,15 @@ class Rule(abc.ABC, metaclass=_Rule):
45
62
  """A summary of what this rule checks for."""
46
63
  return self.__doc__ or ""
47
64
 
48
- def violation(self, violation_msg: t.Optional[str] = None) -> RuleViolation:
65
+ def violation(
66
+ self,
67
+ violation_msg: t.Optional[str] = None,
68
+ violation_range: t.Optional[Range] = None,
69
+ ) -> RuleViolation:
49
70
  """Create a RuleViolation instance for this rule"""
50
- return RuleViolation(rule=self, violation_msg=violation_msg or self.summary)
71
+ return RuleViolation(
72
+ rule=self, violation_msg=violation_msg or self.summary, violation_range=violation_range
73
+ )
51
74
 
52
75
  def get_definition_location(self) -> RuleLocation:
53
76
  """Return the file path and position information for this rule.
@@ -79,9 +102,12 @@ class Rule(abc.ABC, metaclass=_Rule):
79
102
 
80
103
 
81
104
  class RuleViolation:
82
- def __init__(self, rule: Rule, violation_msg: str) -> None:
105
+ def __init__(
106
+ self, rule: Rule, violation_msg: str, violation_range: t.Optional[Range] = None
107
+ ) -> None:
83
108
  self.rule = rule
84
109
  self.violation_msg = violation_msg
110
+ self.violation_range = violation_range
85
111
 
86
112
  def __repr__(self) -> str:
87
113
  return f"{self.rule.name}: {self.violation_msg}"
@@ -4,9 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import typing as t
6
6
 
7
+ from sqlglot.expressions import Star
7
8
  from sqlglot.helper import subclasses
8
9
 
9
- from sqlmesh.core.linter.rule import Rule, RuleViolation
10
+ from sqlmesh.core.linter.helpers import TokenPositionDetails
11
+ from sqlmesh.core.linter.rule import Rule, RuleViolation, Range
10
12
  from sqlmesh.core.linter.definition import RuleSet
11
13
  from sqlmesh.core.model import Model, SqlModel
12
14
 
@@ -15,10 +17,25 @@ class NoSelectStar(Rule):
15
17
  """Query should not contain SELECT * on its outer most projections, even if it can be expanded."""
16
18
 
17
19
  def check_model(self, model: Model) -> t.Optional[RuleViolation]:
20
+ # Only applies to SQL models, as other model types do not have a query.
18
21
  if not isinstance(model, SqlModel):
19
22
  return None
20
-
21
- return self.violation() if model.query.is_star else None
23
+ if model.query.is_star:
24
+ violation_range = self._get_range(model)
25
+ return self.violation(violation_range=violation_range)
26
+ return None
27
+
28
+ def _get_range(self, model: SqlModel) -> t.Optional[Range]:
29
+ """Get the range of the violation if available."""
30
+ try:
31
+ if len(model.query.expressions) == 1 and isinstance(model.query.expressions[0], Star):
32
+ return TokenPositionDetails.from_meta(model.query.expressions[0].meta).to_range(
33
+ None
34
+ )
35
+ except Exception:
36
+ pass
37
+
38
+ return None
22
39
 
23
40
 
24
41
  class InvalidSelectStarExpansion(Rule):
sqlmesh/lsp/context.py CHANGED
@@ -5,7 +5,7 @@ import typing as t
5
5
 
6
6
  from sqlmesh.core.model.definition import SqlModel
7
7
  from sqlmesh.core.linter.definition import AnnotatedRuleViolation
8
- from sqlmesh.lsp.custom import RenderModelEntry, ModelForRendering
8
+ from sqlmesh.lsp.custom import ModelForRendering
9
9
  from sqlmesh.lsp.custom import AllModelsResponse, RenderModelEntry
10
10
  from sqlmesh.lsp.uri import URI
11
11
 
sqlmesh/lsp/hints.py ADDED
@@ -0,0 +1,136 @@
1
+ """Type hinting on SQLMesh models"""
2
+
3
+ import typing as t
4
+
5
+ from lsprotocol import types
6
+
7
+ from sqlglot import exp
8
+ from sqlglot.expressions import Expression
9
+ from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
10
+ from sqlmesh.core.model.definition import SqlModel
11
+ from sqlmesh.lsp.context import LSPContext, ModelTarget
12
+ from sqlmesh.lsp.uri import URI
13
+
14
+
15
+ def get_hints(
16
+ lsp_context: LSPContext,
17
+ document_uri: URI,
18
+ start_line: int,
19
+ end_line: int,
20
+ ) -> t.List[types.InlayHint]:
21
+ """
22
+ Get type hints for certain lines in a document
23
+
24
+ Args:
25
+ lint_context: The LSP context
26
+ document_uri: The URI of the document
27
+ start_line: the starting line to get hints for
28
+ end_line: the ending line to get hints for
29
+
30
+ Returns:
31
+ A list of hints to apply to the document
32
+ """
33
+ path = document_uri.to_path()
34
+ if path.suffix != ".sql":
35
+ return []
36
+
37
+ if path not in lsp_context.map:
38
+ return []
39
+
40
+ file_info = lsp_context.map[path]
41
+
42
+ # Process based on whether it's a model or standalone audit
43
+ if not isinstance(file_info, ModelTarget):
44
+ return []
45
+
46
+ # It's a model
47
+ model = lsp_context.context.get_model(
48
+ model_or_snapshot=file_info.names[0], raise_if_missing=False
49
+ )
50
+ if not isinstance(model, SqlModel):
51
+ return []
52
+
53
+ query = model.query
54
+ dialect = model.dialect
55
+ columns_to_types = model.columns_to_types or {}
56
+
57
+ return _get_type_hints_for_model_from_query(
58
+ query, dialect, columns_to_types, start_line, end_line
59
+ )
60
+
61
+
62
+ def _get_type_hints_for_select(
63
+ expression: exp.Expression,
64
+ dialect: str,
65
+ columns_to_types: t.Dict[str, exp.DataType],
66
+ start_line: int,
67
+ end_line: int,
68
+ ) -> t.List[types.InlayHint]:
69
+ hints: t.List[types.InlayHint] = []
70
+
71
+ for select_exp in expression.expressions:
72
+ if isinstance(select_exp, exp.Alias):
73
+ if isinstance(select_exp.this, exp.Cast):
74
+ continue
75
+
76
+ meta = select_exp.args["alias"].meta
77
+
78
+ elif isinstance(select_exp, exp.Column):
79
+ meta = select_exp.parts[-1].meta
80
+ else:
81
+ continue
82
+
83
+ if "line" not in meta or "col" not in meta:
84
+ continue
85
+
86
+ line = meta["line"]
87
+ col = meta["col"]
88
+
89
+ # Lines from sqlglot are 1 based
90
+ line -= 1
91
+
92
+ if line < start_line or line > end_line:
93
+ continue
94
+
95
+ name = select_exp.alias_or_name
96
+ data_type = columns_to_types.get(name)
97
+
98
+ if not data_type or data_type.is_type(exp.DataType.Type.UNKNOWN):
99
+ continue
100
+
101
+ type_label = data_type.sql(dialect)
102
+ hints.append(
103
+ types.InlayHint(
104
+ label=f"::{type_label}",
105
+ kind=types.InlayHintKind.Type,
106
+ padding_left=False,
107
+ padding_right=True,
108
+ position=types.Position(line=line, character=col),
109
+ )
110
+ )
111
+
112
+ return hints
113
+
114
+
115
+ def _get_type_hints_for_model_from_query(
116
+ query: Expression,
117
+ dialect: str,
118
+ columns_to_types: t.Dict[str, exp.DataType],
119
+ start_line: int,
120
+ end_line: int,
121
+ ) -> t.List[types.InlayHint]:
122
+ hints: t.List[types.InlayHint] = []
123
+ try:
124
+ query = normalize_identifiers(query.copy(), dialect=dialect)
125
+
126
+ # Return the hints for top level selects (model definition columns only)
127
+ return [
128
+ hint
129
+ for q in query.walk(prune=lambda n: not isinstance(n, exp.SetOperation))
130
+ if isinstance(select := q.unnest(), exp.Select)
131
+ for hint in _get_type_hints_for_select(
132
+ q, dialect, columns_to_types, start_line, end_line
133
+ )
134
+ ]
135
+ except Exception:
136
+ return []
sqlmesh/lsp/main.py CHANGED
@@ -36,6 +36,7 @@ from sqlmesh.lsp.custom import (
36
36
  RenderModelRequest,
37
37
  RenderModelResponse,
38
38
  )
39
+ from sqlmesh.lsp.hints import get_hints
39
40
  from sqlmesh.lsp.reference import get_references, get_cte_references
40
41
  from sqlmesh.lsp.uri import URI
41
42
  from web.server.api.endpoints.lineage import column_lineage, model_lineage
@@ -334,6 +335,25 @@ class SQLMeshLanguageServer:
334
335
  )
335
336
  return None
336
337
 
338
+ @self.server.feature(types.TEXT_DOCUMENT_INLAY_HINT)
339
+ def inlay_hint(
340
+ ls: LanguageServer, params: types.InlayHintParams
341
+ ) -> t.List[types.InlayHint]:
342
+ """Implement type hints for sql columns as inlay hints"""
343
+ try:
344
+ uri = URI(params.text_document.uri)
345
+ self._ensure_context_for_document(uri)
346
+ if self.lsp_context is None:
347
+ raise RuntimeError(f"No context found for document: {uri}")
348
+
349
+ start_line = params.range.start.line
350
+ end_line = params.range.end.line
351
+ hints = get_hints(self.lsp_context, uri, start_line, end_line)
352
+ return hints
353
+
354
+ except Exception as e:
355
+ return []
356
+
337
357
  @self.server.feature(types.TEXT_DOCUMENT_DEFINITION)
338
358
  def goto_definition(
339
359
  ls: LanguageServer, params: types.DefinitionParams
@@ -655,8 +675,24 @@ class SQLMeshLanguageServer:
655
675
  ) -> t.Optional[types.Diagnostic]:
656
676
  if diagnostic.model._path is None:
657
677
  return None
658
- with open(diagnostic.model._path, "r", encoding="utf-8") as file:
659
- lines = file.readlines()
678
+ if not diagnostic.violation_range:
679
+ with open(diagnostic.model._path, "r", encoding="utf-8") as file:
680
+ lines = file.readlines()
681
+ range = types.Range(
682
+ start=types.Position(line=0, character=0),
683
+ end=types.Position(line=len(lines) - 1, character=len(lines[-1])),
684
+ )
685
+ else:
686
+ range = types.Range(
687
+ start=types.Position(
688
+ line=diagnostic.violation_range.start.line,
689
+ character=diagnostic.violation_range.start.character,
690
+ ),
691
+ end=types.Position(
692
+ line=diagnostic.violation_range.end.line,
693
+ character=diagnostic.violation_range.end.character,
694
+ ),
695
+ )
660
696
 
661
697
  # Get rule definition location for diagnostics link
662
698
  rule_location = diagnostic.rule.get_definition_location()
@@ -665,10 +701,7 @@ class SQLMeshLanguageServer:
665
701
 
666
702
  # Use URI format to create a link for "related information"
667
703
  return types.Diagnostic(
668
- range=types.Range(
669
- start=types.Position(line=0, character=0),
670
- end=types.Position(line=len(lines), character=len(lines[-1])),
671
- ),
704
+ range=range,
672
705
  message=diagnostic.violation_msg,
673
706
  severity=types.DiagnosticSeverity.Error
674
707
  if diagnostic.violation_type == "error"
sqlmesh/lsp/reference.py CHANGED
@@ -4,6 +4,11 @@ from pathlib import Path
4
4
 
5
5
  from sqlmesh.core.audit import StandaloneAudit
6
6
  from sqlmesh.core.dialect import normalize_model_name
7
+ from sqlmesh.core.linter.helpers import (
8
+ TokenPositionDetails,
9
+ Range as SQLMeshRange,
10
+ Position as SQLMeshPosition,
11
+ )
7
12
  from sqlmesh.core.model.definition import SqlModel
8
13
  from sqlmesh.lsp.context import LSPContext, ModelTarget, AuditTarget
9
14
  from sqlglot import exp
@@ -156,12 +161,17 @@ def get_model_definitions_for_a_path(
156
161
  if isinstance(alias, exp.TableAlias):
157
162
  identifier = alias.this
158
163
  if isinstance(identifier, exp.Identifier):
159
- target_range = _range_from_token_position_details(
160
- TokenPositionDetails.from_meta(identifier.meta), read_file
161
- )
162
- table_range = _range_from_token_position_details(
163
- TokenPositionDetails.from_meta(table.this.meta), read_file
164
- )
164
+ target_range_sqlmesh = TokenPositionDetails.from_meta(
165
+ identifier.meta
166
+ ).to_range(read_file)
167
+ table_range_sqlmesh = TokenPositionDetails.from_meta(
168
+ table.this.meta
169
+ ).to_range(read_file)
170
+
171
+ # Convert SQLMesh Range to LSP Range
172
+ target_range = to_lsp_range(target_range_sqlmesh)
173
+ table_range = to_lsp_range(table_range_sqlmesh)
174
+
165
175
  references.append(
166
176
  Reference(
167
177
  uri=document_uri.value, # Same file
@@ -203,25 +213,26 @@ def get_model_definitions_for_a_path(
203
213
 
204
214
  # Extract metadata for positioning
205
215
  table_meta = TokenPositionDetails.from_meta(table.this.meta)
206
- table_range = _range_from_token_position_details(table_meta, read_file)
207
- start_pos = table_range.start
208
- end_pos = table_range.end
216
+ table_range_sqlmesh = table_meta.to_range(read_file)
217
+ start_pos_sqlmesh = table_range_sqlmesh.start
218
+ end_pos_sqlmesh = table_range_sqlmesh.end
209
219
 
210
220
  # If there's a catalog or database qualifier, adjust the start position
211
221
  catalog_or_db = table.args.get("catalog") or table.args.get("db")
212
222
  if catalog_or_db is not None:
213
223
  catalog_or_db_meta = TokenPositionDetails.from_meta(catalog_or_db.meta)
214
- catalog_or_db_range = _range_from_token_position_details(
215
- catalog_or_db_meta, read_file
216
- )
217
- start_pos = catalog_or_db_range.start
224
+ catalog_or_db_range_sqlmesh = catalog_or_db_meta.to_range(read_file)
225
+ start_pos_sqlmesh = catalog_or_db_range_sqlmesh.start
218
226
 
219
227
  description = generate_markdown_description(referenced_model)
220
228
 
221
229
  references.append(
222
230
  Reference(
223
231
  uri=referenced_model_uri.value,
224
- range=Range(start=start_pos, end=end_pos),
232
+ range=Range(
233
+ start=to_lsp_position(start_pos_sqlmesh),
234
+ end=to_lsp_position(end_pos_sqlmesh),
235
+ ),
225
236
  markdown_description=description,
226
237
  )
227
238
  )
@@ -229,71 +240,6 @@ def get_model_definitions_for_a_path(
229
240
  return references
230
241
 
231
242
 
232
- class TokenPositionDetails(PydanticModel):
233
- """
234
- Details about a token's position in the source code.
235
-
236
- Attributes:
237
- line (int): The line that the token ends on.
238
- col (int): The column that the token ends on.
239
- start (int): The start index of the token.
240
- end (int): The ending index of the token.
241
- """
242
-
243
- line: int
244
- col: int
245
- start: int
246
- end: int
247
-
248
- @staticmethod
249
- def from_meta(meta: t.Dict[str, int]) -> "TokenPositionDetails":
250
- return TokenPositionDetails(
251
- line=meta["line"],
252
- col=meta["col"],
253
- start=meta["start"],
254
- end=meta["end"],
255
- )
256
-
257
-
258
- def _range_from_token_position_details(
259
- token_position_details: TokenPositionDetails, read_file: t.List[str]
260
- ) -> Range:
261
- """
262
- Convert a TokenPositionDetails object to a Range object.
263
-
264
- :param token_position_details: Details about a token's position
265
- :param read_file: List of lines from the file
266
- :return: A Range object representing the token's position
267
- """
268
- # Convert from 1-indexed to 0-indexed for line only
269
- end_line_0 = token_position_details.line - 1
270
- end_col_0 = token_position_details.col
271
-
272
- # Find the start line and column by counting backwards from the end position
273
- start_pos = token_position_details.start
274
- end_pos = token_position_details.end
275
-
276
- # Initialize with the end position
277
- start_line_0 = end_line_0
278
- start_col_0 = end_col_0 - (end_pos - start_pos + 1)
279
-
280
- # If start_col_0 is negative, we need to go back to previous lines
281
- while start_col_0 < 0 and start_line_0 > 0:
282
- start_line_0 -= 1
283
- start_col_0 += len(read_file[start_line_0])
284
- # Account for newline character
285
- if start_col_0 >= 0:
286
- break
287
- start_col_0 += 1 # For the newline character
288
-
289
- # Ensure we don't have negative values
290
- start_col_0 = max(0, start_col_0)
291
- return Range(
292
- start=Position(line=start_line_0, character=start_col_0),
293
- end=Position(line=end_line_0, character=end_col_0),
294
- )
295
-
296
-
297
243
  def get_macro_definitions_for_a_path(
298
244
  lsp_context: LSPContext, document_uri: URI
299
245
  ) -> t.List[Reference]:
@@ -373,11 +319,10 @@ def get_macro_reference(
373
319
  try:
374
320
  # Get the position of the macro invocation in the source file first
375
321
  if hasattr(node, "meta") and node.meta:
376
- token_details = TokenPositionDetails.from_meta(node.meta)
377
- macro_range = _range_from_token_position_details(token_details, read_file)
322
+ macro_range = TokenPositionDetails.from_meta(node.meta).to_range(read_file)
378
323
 
379
324
  # Check if it's a built-in method
380
- if builtin := get_built_in_macro_reference(macro_name, macro_range):
325
+ if builtin := get_built_in_macro_reference(macro_name, to_lsp_range(macro_range)):
381
326
  return builtin
382
327
  else:
383
328
  # Skip if we can't get the position
@@ -429,7 +374,7 @@ def get_macro_reference(
429
374
 
430
375
  return Reference(
431
376
  uri=macro_uri.value,
432
- range=macro_range,
377
+ range=to_lsp_range(macro_range),
433
378
  target_range=Range(
434
379
  start=Position(line=start_line - 1, character=0),
435
380
  end=Position(line=end_line - 1, character=get_length_of_end_line),
@@ -544,3 +489,24 @@ def _position_within_range(position: Position, range: Range) -> bool:
544
489
  range.end.line > position.line
545
490
  or (range.end.line == position.line and range.end.character >= position.character)
546
491
  )
492
+
493
+
494
+ def to_lsp_range(
495
+ range: SQLMeshRange,
496
+ ) -> Range:
497
+ """
498
+ Converts a SQLMesh Range to an LSP Range.
499
+ """
500
+ return Range(
501
+ start=Position(line=range.start.line, character=range.start.character),
502
+ end=Position(line=range.end.line, character=range.end.character),
503
+ )
504
+
505
+
506
+ def to_lsp_position(
507
+ position: SQLMeshPosition,
508
+ ) -> Position:
509
+ """
510
+ Converts a SQLMesh Position to an LSP Position.
511
+ """
512
+ return Position(line=position.line, character=position.character)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlmesh
3
- Version: 0.189.2.dev5
3
+ Version: 0.189.3.dev1
4
4
  Summary: Next-generation data transformation framework
5
5
  Author-email: "TobikoData Inc." <engineering@tobikodata.com>
6
6
  License: Apache License
@@ -1,5 +1,5 @@
1
1
  sqlmesh/__init__.py,sha256=QjwaamJIdW7voXoS880yAx7tHm-Im4_mqGA-XJgafos,7631
2
- sqlmesh/_version.py,sha256=UCkyVwo8pASnW_g9KnP7JuTaDH8HiF1OwE6T433YP3A,528
2
+ sqlmesh/_version.py,sha256=46P0A4hbz6tPxXHU78sPmL24cECOw9bp_3B8jB9kTr8,528
3
3
  sqlmesh/magics.py,sha256=fu7-OeT_0_TAkWZOLoiioE3-3iFpvTRxFCbQUlRnfuw,39944
4
4
  sqlmesh/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  sqlmesh/cicd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -76,10 +76,11 @@ sqlmesh/core/engine_adapter/snowflake.py,sha256=oaZxKPmPXxmr46NQfw6OI5vPwvaYJ3Ym
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=UL05OjFBfoLSYGWXNf4zjZYzIKr-OyFIMIcIh7RhR8s,5237
80
- sqlmesh/core/linter/rule.py,sha256=p8rbMbf2Z18ZJ6ZcZyoCk-3Lvo7d_Gvc35tiwWXvuQs,2579
79
+ sqlmesh/core/linter/definition.py,sha256=Rjqvto7VvVEnMXXOH4q3Jol1HnPe6I6BCHK1sEJm50I,5430
80
+ sqlmesh/core/linter/helpers.py,sha256=VF4hsdzGZ8srpbtCjdRN_Hd8S_er5KTAaLXLde27Yxo,4032
81
+ sqlmesh/core/linter/rule.py,sha256=iCmFUw6Qx7GuUwLKIpHrHdq3kgtgm5JC2t9VFtMY3BQ,3180
81
82
  sqlmesh/core/linter/rules/__init__.py,sha256=gevzfb67vFqckTCoVAe_TBGf6hQ-YtE1_YuGuXyh1L0,77
82
- sqlmesh/core/linter/rules/builtin.py,sha256=RXns3e042q8aM-3wDUNDc7dcX5hDiOO7qel1YnjeqUk,1950
83
+ sqlmesh/core/linter/rules/builtin.py,sha256=EHTd4sgHT6oT93onK7t5pLmv2KgzZ81twkTnOrMi9tI,2681
83
84
  sqlmesh/core/metric/__init__.py,sha256=H1HmoD5IwN4YWe9iJXyueLYNmTQFZwok5nSWNJcZIBQ,237
84
85
  sqlmesh/core/metric/definition.py,sha256=Yd5aVgsZCDPJ43aGP7WqtzZOuuSUtB8uJGVA6Jw9x9M,7201
85
86
  sqlmesh/core/metric/rewriter.py,sha256=GiSTHfn2kinqCfNPYgZPRk93JFLzVaaejHtHDQ0yXZI,7326
@@ -159,11 +160,12 @@ sqlmesh/integrations/github/cicd/config.py,sha256=tRhMZaLf1p6AWXvi67n85Q-lptJNGt
159
160
  sqlmesh/integrations/github/cicd/controller.py,sha256=IgUWK9orYSMiElO3GaL6Ja-osFgA7je3RDcLChWUi5s,43769
160
161
  sqlmesh/lsp/api.py,sha256=LJdECSaEZoPbu_Xl4KscVG-WLt7_g4dY-zKGO68dH14,2162
161
162
  sqlmesh/lsp/completions.py,sha256=Y6zNKNne91tzRm5vRk82JmhIpo7dIBUoK8GGQesGlT8,5746
162
- sqlmesh/lsp/context.py,sha256=RVFGjKWv1o509su80izZL-Wqy8vWElqUDnv2XWvBsgs,6271
163
+ sqlmesh/lsp/context.py,sha256=36bUTiMSyfX91bri_F1aivDvSllq0rKidIM_RGAtirs,6253
163
164
  sqlmesh/lsp/custom.py,sha256=Mml6V1nYAHdP4Zt8gxly1ZLKYALtxGeAVzPrrYpc7m8,1426
164
165
  sqlmesh/lsp/description.py,sha256=4QIGC9ix8IzmWF6DVUNhY4m1Crk3sNQA6hZ1RbyqU78,827
165
- sqlmesh/lsp/main.py,sha256=GlqGFQgI1e23UaOQmeFr9jbSCC3zcbQiM_EEMSCiE90,32237
166
- sqlmesh/lsp/reference.py,sha256=_fXzPDjyDy1AgWtDUMpN9PWk7h98LZt0uMW-llbNl7g,19083
166
+ sqlmesh/lsp/hints.py,sha256=j-blccBUil94yRoDjWfkSM0HqMmsKD6hP2TaWdWWVk4,3734
167
+ sqlmesh/lsp/main.py,sha256=c1h1hWluBixEFZEX2jiBjA_EOgguruaG9cSLjCrEPr8,33581
168
+ sqlmesh/lsp/reference.py,sha256=dyeacD_MHATGRIrl8Ax86nDK9EC_dh3TaaVNAepPce0,17902
167
169
  sqlmesh/lsp/uri.py,sha256=mvSLbZGmMTHPpliThOtVWiFcHnfJzp1w39q9l-xD2SI,802
168
170
  sqlmesh/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
169
171
  sqlmesh/migrations/v0001_init.py,sha256=h8FwMYJj9_O8yGpk12guaSaGwX5A3we2GiKeHrXi15o,2048
@@ -272,7 +274,7 @@ sqlmesh/utils/pydantic.py,sha256=I898jAFqa3qU6lG2uPpRebVFncOUo8ZrLWM_R3K3DHU,116
272
274
  sqlmesh/utils/rich.py,sha256=xNZyPCqEc4TAlJlsEthg-5eYUWpaizz0QjBzO7a1Yv4,1509
273
275
  sqlmesh/utils/windows.py,sha256=r7Yin4WEwbvH7sKA3hOJtRsclmscNuBeCklCfVPvtQs,611
274
276
  sqlmesh/utils/yaml.py,sha256=np-WVcwmwsKghCgAj1OBBMsK1SXSRn4wR-H37cGiFr8,2282
275
- sqlmesh-0.189.2.dev5.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
277
+ sqlmesh-0.189.3.dev1.dist-info/licenses/LICENSE,sha256=OlMefUjgWJdULtf84BLW0AZZcY8DwdgQqb_1j2862j8,11346
276
278
  web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
277
279
  web/client/dist/index.html,sha256=LbR9XrOXE77otBPoDnaomssYz6h2TBwLANs6hUJ0p2M,5472
278
280
  web/client/dist/assets/Audits-DB1HcP9M.js,sha256=eGI_Soj69Jntsixw1NqQpdwG94N5jipQZv7T1Y7WUSs,1041
@@ -390,8 +392,8 @@ web/server/api/endpoints/models.py,sha256=ud-lRwriyI_OYg9tZchrBgeiXELiRTLNEodraf
390
392
  web/server/api/endpoints/modules.py,sha256=8hqqgonGay_mJmpCw0IdbjsPhWlQH2VLdKAqha-myac,468
391
393
  web/server/api/endpoints/plan.py,sha256=OYuQ_4dgbhG9ibTcX1a8mvYgH3ANxw9Fhbb4wg6QWqc,9222
392
394
  web/server/api/endpoints/table_diff.py,sha256=SCy3-pr5x6bEEiOaZGCDbiHQhpgxwmnz3Acqa2Ie6jI,2090
393
- sqlmesh-0.189.2.dev5.dist-info/METADATA,sha256=f4UGuR824Wi94aH6tQXKt6YbvnTjINkev0MqnYB5-PM,26540
394
- sqlmesh-0.189.2.dev5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
395
- sqlmesh-0.189.2.dev5.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
396
- sqlmesh-0.189.2.dev5.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
397
- sqlmesh-0.189.2.dev5.dist-info/RECORD,,
395
+ sqlmesh-0.189.3.dev1.dist-info/METADATA,sha256=S9cWZvOMCb4TkPpsuwJOjHs_Bcbiddm0657q1eWV8jE,26540
396
+ sqlmesh-0.189.3.dev1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
397
+ sqlmesh-0.189.3.dev1.dist-info/entry_points.txt,sha256=pIYKtg-u-uO7BK_Y0qrdmfyj0bYrcMc3Xiqr5WE3VFk,121
398
+ sqlmesh-0.189.3.dev1.dist-info/top_level.txt,sha256=bspjo5vjELQkuN0qKuTFIbsrUt3Zeo8D2mlKeHG1LdE,12
399
+ sqlmesh-0.189.3.dev1.dist-info/RECORD,,