sqllens 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +2 -74
  2. package/THIRD-PARTY-NOTICES.md +50 -5
  3. package/dist/api.d.ts +5 -3
  4. package/dist/api.js +11 -4
  5. package/dist/bigquery/behavior.d.ts +2 -0
  6. package/dist/bigquery/behavior.js +20 -0
  7. package/dist/bigquery/dot-path.d.ts +0 -2
  8. package/dist/bigquery/dot-path.js +0 -1
  9. package/dist/bigquery/fold.d.ts +8 -0
  10. package/dist/bigquery/fold.js +39 -0
  11. package/dist/bigquery/index.d.ts +7 -0
  12. package/dist/bigquery/index.js +10 -0
  13. package/dist/{infer/bigquery.d.ts → bigquery/infer.d.ts} +2 -2
  14. package/dist/{infer/bigquery.js → bigquery/infer.js} +4 -3
  15. package/dist/bigquery/lower.js +35 -12
  16. package/dist/bigquery/signatures.generated.d.ts +6 -0
  17. package/dist/bigquery/signatures.generated.js +1068 -0
  18. package/dist/completion/atn-walk.d.ts +13 -2
  19. package/dist/completion/atn-walk.js +13 -10
  20. package/dist/completion/complete.d.ts +4 -3
  21. package/dist/completion/complete.js +101 -45
  22. package/dist/completion/jinja-slot.d.ts +24 -0
  23. package/dist/completion/jinja-slot.js +126 -0
  24. package/dist/completion/parser-factory.d.ts +13 -1
  25. package/dist/completion/parser-factory.js +12 -0
  26. package/dist/databricks/behavior.d.ts +2 -0
  27. package/dist/databricks/behavior.js +19 -0
  28. package/dist/databricks/fold.d.ts +8 -0
  29. package/dist/databricks/fold.js +27 -0
  30. package/dist/databricks/index.d.ts +7 -0
  31. package/dist/databricks/index.js +10 -0
  32. package/dist/databricks/infer.d.ts +6 -0
  33. package/dist/databricks/infer.js +638 -0
  34. package/dist/databricks/signatures.generated.d.ts +6 -0
  35. package/dist/databricks/signatures.generated.js +1745 -0
  36. package/dist/dialect-behavior/behavior.d.ts +26 -0
  37. package/dist/dialect-behavior/behavior.js +1 -0
  38. package/dist/dialect-behavior/carrier.d.ts +5 -0
  39. package/dist/dialect-behavior/carrier.js +5 -0
  40. package/dist/dialect-behavior/coerce-rules.d.ts +7 -0
  41. package/dist/dialect-behavior/coerce-rules.js +69 -0
  42. package/dist/dialect-behavior/public-fold.d.ts +6 -0
  43. package/dist/dialect-behavior/public-fold.js +9 -0
  44. package/dist/dialect-behavior/registry.d.ts +6 -0
  45. package/dist/dialect-behavior/registry.js +34 -0
  46. package/dist/dialect-symbols.js +16 -19
  47. package/dist/document/document.d.ts +1 -1
  48. package/dist/document/document.js +8 -7
  49. package/dist/duckdb/behavior.d.ts +2 -0
  50. package/dist/duckdb/behavior.js +21 -0
  51. package/dist/duckdb/fold.d.ts +8 -0
  52. package/dist/duckdb/fold.js +29 -0
  53. package/dist/duckdb/index.d.ts +7 -0
  54. package/dist/duckdb/index.js +10 -0
  55. package/dist/{infer/duckdb.d.ts → duckdb/infer.d.ts} +2 -2
  56. package/dist/{infer/duckdb.js → duckdb/infer.js} +4 -3
  57. package/dist/duckdb/lower.js +47 -15
  58. package/dist/duckdb/signatures.generated.d.ts +6 -0
  59. package/dist/duckdb/signatures.generated.js +1072 -0
  60. package/dist/generated/bigquery/GoogleSQLParser.js +0 -7060
  61. package/dist/generated/databricks/DatabricksParser.js +0 -4800
  62. package/dist/generated/duckdb/DuckdbParser.js +0 -9100
  63. package/dist/generated/minijinja/MinijinjaParser.js +0 -380
  64. package/dist/generated/mysql/MysqlParser.js +0 -6330
  65. package/dist/generated/postgres/PostgresParser.js +0 -8530
  66. package/dist/generated/redshift/RedshiftParser.js +0 -10970
  67. package/dist/generated/snowflake/SnowflakeParser.js +0 -7320
  68. package/dist/generated/sqlite/SqliteParser.js +0 -1150
  69. package/dist/generated/trino/TrinoParser.js +0 -3770
  70. package/dist/generated/tsql/TSqlParser.js +0 -8420
  71. package/dist/ident/fold.d.ts +24 -15
  72. package/dist/ident/fold.js +12 -194
  73. package/dist/index.d.ts +2 -2
  74. package/dist/index.js +10 -8
  75. package/dist/infer/functions.d.ts +22 -11
  76. package/dist/infer/functions.js +26 -888
  77. package/dist/infer/infer.js +16 -14
  78. package/dist/infer/nullability.js +3 -4
  79. package/dist/infer/types.d.ts +1 -1
  80. package/dist/infer/types.js +7 -7
  81. package/dist/ir/ir.d.ts +26 -17
  82. package/dist/ir/span.js +0 -2
  83. package/dist/ir/walk.js +3 -3
  84. package/dist/lineage/hops.js +13 -10
  85. package/dist/lineage/lineage.js +10 -7
  86. package/dist/minijinja/apply-tags.d.ts +14 -7
  87. package/dist/minijinja/apply-tags.js +66 -121
  88. package/dist/minijinja/parse.js +5 -5
  89. package/dist/minijinja/tag-ast.d.ts +29 -36
  90. package/dist/minijinja/tag-ast.js +210 -90
  91. package/dist/mysql/behavior.d.ts +2 -0
  92. package/dist/mysql/behavior.js +21 -0
  93. package/dist/mysql/fold.d.ts +8 -0
  94. package/dist/mysql/fold.js +49 -0
  95. package/dist/mysql/index.d.ts +7 -0
  96. package/dist/mysql/index.js +10 -0
  97. package/dist/{infer/mysql.d.ts → mysql/infer.d.ts} +2 -2
  98. package/dist/{infer/mysql.js → mysql/infer.js} +3 -2
  99. package/dist/mysql/signatures.generated.d.ts +6 -0
  100. package/dist/mysql/signatures.generated.js +508 -0
  101. package/dist/postgres/behavior.d.ts +2 -0
  102. package/dist/postgres/behavior.js +19 -0
  103. package/dist/postgres/fold.d.ts +8 -0
  104. package/dist/postgres/fold.js +30 -0
  105. package/dist/postgres/index.d.ts +7 -0
  106. package/dist/postgres/index.js +10 -0
  107. package/dist/{infer/postgres.d.ts → postgres/infer.d.ts} +2 -2
  108. package/dist/{infer/postgres.js → postgres/infer.js} +4 -3
  109. package/dist/postgres/lower.js +2 -2
  110. package/dist/postgres/signatures.generated.d.ts +6 -0
  111. package/dist/postgres/signatures.generated.js +2973 -0
  112. package/dist/qualify/check-calls.js +80 -161
  113. package/dist/qualify/qualify.js +16 -14
  114. package/dist/qualify/schema-provider.js +2 -2
  115. package/dist/qualify/schema.js +3 -3
  116. package/dist/qualify/template-provider.d.ts +45 -12
  117. package/dist/qualify/template-provider.js +69 -38
  118. package/dist/redshift/behavior.d.ts +2 -0
  119. package/dist/redshift/behavior.js +19 -0
  120. package/dist/redshift/fold.d.ts +8 -0
  121. package/dist/redshift/fold.js +31 -0
  122. package/dist/redshift/index.d.ts +7 -0
  123. package/dist/redshift/index.js +10 -0
  124. package/dist/{infer/redshift.d.ts → redshift/infer.d.ts} +2 -2
  125. package/dist/{infer/redshift.js → redshift/infer.js} +4 -3
  126. package/dist/redshift/lower.js +2 -2
  127. package/dist/redshift/signatures.generated.d.ts +6 -0
  128. package/dist/redshift/signatures.generated.js +757 -0
  129. package/dist/references/references.js +17 -12
  130. package/dist/scope/like-pattern.d.ts +2 -0
  131. package/dist/scope/like-pattern.js +15 -0
  132. package/dist/scope/scope.d.ts +5 -5
  133. package/dist/scope/scope.js +48 -42
  134. package/dist/sema/resolve.js +17 -12
  135. package/dist/session.d.ts +2 -2
  136. package/dist/signature/signature.d.ts +14 -6
  137. package/dist/signature/signature.js +30 -22
  138. package/dist/signature/signatures.d.ts +14 -12
  139. package/dist/signature/signatures.js +42 -721
  140. package/dist/snowflake/behavior.d.ts +2 -0
  141. package/dist/snowflake/behavior.js +22 -0
  142. package/dist/snowflake/fold.d.ts +8 -0
  143. package/dist/snowflake/fold.js +25 -0
  144. package/dist/snowflake/index.d.ts +7 -0
  145. package/dist/snowflake/index.js +10 -0
  146. package/dist/{infer/snowflake.d.ts → snowflake/infer.d.ts} +2 -2
  147. package/dist/{infer/snowflake.js → snowflake/infer.js} +4 -3
  148. package/dist/snowflake/lower.js +59 -19
  149. package/dist/snowflake/signatures.generated.d.ts +6 -0
  150. package/dist/snowflake/signatures.generated.js +2080 -0
  151. package/dist/sqlite/behavior.d.ts +2 -0
  152. package/dist/sqlite/behavior.js +19 -0
  153. package/dist/sqlite/fold.d.ts +8 -0
  154. package/dist/sqlite/fold.js +41 -0
  155. package/dist/sqlite/index.d.ts +7 -0
  156. package/dist/sqlite/index.js +10 -0
  157. package/dist/{infer/sqlite.d.ts → sqlite/infer.d.ts} +2 -2
  158. package/dist/{infer/sqlite.js → sqlite/infer.js} +4 -3
  159. package/dist/sqlite/signatures.generated.d.ts +6 -0
  160. package/dist/sqlite/signatures.generated.js +277 -0
  161. package/dist/symbols/symbols.js +14 -12
  162. package/dist/trino/behavior.d.ts +2 -0
  163. package/dist/trino/behavior.js +21 -0
  164. package/dist/trino/fold.d.ts +8 -0
  165. package/dist/trino/fold.js +39 -0
  166. package/dist/trino/index.d.ts +7 -0
  167. package/dist/trino/index.js +10 -0
  168. package/dist/{infer/trino.d.ts → trino/infer.d.ts} +2 -2
  169. package/dist/{infer/trino.js → trino/infer.js} +4 -3
  170. package/dist/trino/lower.js +5 -5
  171. package/dist/trino/signatures.generated.d.ts +6 -0
  172. package/dist/trino/signatures.generated.js +968 -0
  173. package/dist/tsql/behavior.d.ts +2 -0
  174. package/dist/tsql/behavior.js +20 -0
  175. package/dist/tsql/fold.d.ts +8 -0
  176. package/dist/tsql/fold.js +34 -0
  177. package/dist/tsql/index.d.ts +7 -0
  178. package/dist/tsql/index.js +10 -0
  179. package/dist/tsql/infer.d.ts +16 -0
  180. package/dist/tsql/infer.js +289 -0
  181. package/dist/tsql/lower.js +4 -4
  182. package/dist/tsql/signatures.generated.d.ts +6 -0
  183. package/dist/tsql/signatures.generated.js +640 -0
  184. package/package.json +5 -10
  185. package/dist/generated/bigquery/GoogleSQLLexer.d.ts +0 -407
  186. package/dist/generated/bigquery/GoogleSQLParser.d.ts +0 -9558
  187. package/dist/generated/bigquery/GoogleSQLParserListener.d.ts +0 -7777
  188. package/dist/generated/bigquery/GoogleSQLParserListener.js +0 -7070
  189. package/dist/generated/databricks/DatabricksLexer.d.ts +0 -566
  190. package/dist/generated/databricks/DatabricksParser.d.ts +0 -7771
  191. package/dist/generated/databricks/DatabricksParserListener.d.ts +0 -5737
  192. package/dist/generated/databricks/DatabricksParserListener.js +0 -5256
  193. package/dist/generated/duckdb/DuckdbLexer.d.ts +0 -691
  194. package/dist/generated/duckdb/DuckdbParser.d.ts +0 -13932
  195. package/dist/generated/duckdb/DuckdbParserListener.d.ts +0 -10049
  196. package/dist/generated/duckdb/DuckdbParserListener.js +0 -9138
  197. package/dist/generated/minijinja/MinijinjaLexer.d.ts +0 -108
  198. package/dist/generated/minijinja/MinijinjaParser.d.ts +0 -604
  199. package/dist/generated/minijinja/MinijinjaParserListener.d.ts +0 -449
  200. package/dist/generated/minijinja/MinijinjaParserListener.js +0 -410
  201. package/dist/generated/mysql/MysqlLexer.d.ts +0 -1198
  202. package/dist/generated/mysql/MysqlParser.d.ts +0 -10992
  203. package/dist/generated/mysql/MysqlParserListener.d.ts +0 -7592
  204. package/dist/generated/mysql/MysqlParserListener.js +0 -6958
  205. package/dist/generated/postgres/PostgresLexer.d.ts +0 -663
  206. package/dist/generated/postgres/PostgresParser.d.ts +0 -12963
  207. package/dist/generated/postgres/PostgresParserListener.d.ts +0 -9408
  208. package/dist/generated/postgres/PostgresParserListener.js +0 -8554
  209. package/dist/generated/redshift/RedshiftLexer.d.ts +0 -954
  210. package/dist/generated/redshift/RedshiftParser.d.ts +0 -16939
  211. package/dist/generated/redshift/RedshiftParserListener.d.ts +0 -12092
  212. package/dist/generated/redshift/RedshiftParserListener.js +0 -10994
  213. package/dist/generated/snowflake/SnowflakeLexer.d.ts +0 -1046
  214. package/dist/generated/snowflake/SnowflakeParser.d.ts +0 -14196
  215. package/dist/generated/snowflake/SnowflakeParserListener.d.ts +0 -8063
  216. package/dist/generated/snowflake/SnowflakeParserListener.js +0 -7330
  217. package/dist/generated/sqlite/SqliteLexer.d.ts +0 -210
  218. package/dist/generated/sqlite/SqliteParser.d.ts +0 -2115
  219. package/dist/generated/sqlite/SqliteParserListener.d.ts +0 -1276
  220. package/dist/generated/sqlite/SqliteParserListener.js +0 -1160
  221. package/dist/generated/trino/TrinoLexer.d.ts +0 -381
  222. package/dist/generated/trino/TrinoParser.d.ts +0 -5340
  223. package/dist/generated/trino/TrinoParserListener.d.ts +0 -4704
  224. package/dist/generated/trino/TrinoParserListener.js +0 -4326
  225. package/dist/generated/tsql/TSqlLexer.d.ts +0 -1278
  226. package/dist/generated/tsql/TSqlParser.d.ts +0 -17267
  227. package/dist/generated/tsql/TSqlParserListener.d.ts +0 -9697
  228. package/dist/generated/tsql/TSqlParserListener.js +0 -8854
  229. package/dist/infer/dialect.d.ts +0 -21
  230. package/dist/infer/dialect.js +0 -100
  231. package/dist/infer/literals.d.ts +0 -6
  232. package/dist/infer/literals.js +0 -44
  233. package/dist/signature/generated/tsql.d.ts +0 -3
  234. package/dist/signature/generated/tsql.js +0 -260
@@ -1,41 +1,36 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Task 4 — R2 tag-AST: ref / source / macro-call nodes with the EXACT span
3
- // contract (docs/minijinja-front-end.md §R2). This is the HARD deliverable: the
4
- // dbt-anvil extension positions hover / rename / signature-help exactly on the
5
- // spans emitted here, so every offset must be document-true.
2
+ // R2 tag-AST: the NEUTRAL tag node with the EXACT span contract
3
+ // (docs/minijinja-front-end.md §R2). This is the HARD deliverable: the editor
4
+ // positions hover / rename / signature-help exactly on the spans emitted here, so
5
+ // every offset must be document-true.
6
6
  //
7
7
  // The walk is a small tree-navigation over the per-tag jinja parse tree, built by
8
- // parse.ts from a DOCUMENT-native token slice (segment.ts's `tagTokens` — the one
8
+ // parse.ts from a DOCUMENT-native token slice (segment.ts's `tagTokens`, the one
9
9
  // whole-document tokenization, sliced per tag via antlr4ng's `ListTokenSource`).
10
10
  // Every token in that slice already carries its real document start/stop/line/
11
11
  // column, so every span read off it is document-true with NO offset/anchor
12
- // composition — the parity UPGRADE over the extension's single-line-lossy regex
13
- // extractors, now by construction rather than by a doc-offset shift.
12
+ // composition.
14
13
  //
15
- // Classification is by the LEADING call name (§R2 / §the hole):
16
- // - a bare `ref(...)` → ref node (model = last positional string)
17
- // - a bare `source(...)` → source node (source + table, both strings)
18
- // - a bare `var(...)`/`env_var(…)`→ that node kind
19
- // - a leading NO_OUTPUT_BUILTIN → config → "config"; the rest → "other"
20
- // (config/docs/print/log/return/exceptions — REUSES segment.ts's set, the
21
- // single source of truth; `exceptions.raise_compiler_error(…)` classifies
22
- // off the leading `exceptions` too)
23
- // - `pkg.macro(...)` or a bare unknown call → macro node
24
- // - any other expr (bare name, literal, arithmetic) → "other"
25
- // - a `{% … %}` statement tag → "control" (inc1 treatment; precise
26
- // if/for/set structure + `{% do macro() %}` macro-in-stmt is inc2)
27
- // - a `{# … #}` comment tag → "other"
14
+ // The tag AST carries NO dbt vocabulary. Every expression tag classifies into one
15
+ // of three neutral kinds:
16
+ // - a leading call `name(args)` (bare or `pkg.name`) → a "call" node. ref/source/
17
+ // config/var/env_var/a user macro are ALL just callees, distinguished by `name`.
18
+ // Whether `ref` denotes a relation is the provider's knowledge, not the tag AST.
19
+ // Each arg carries {value, span, valueSpan}: a consumer that knows a call's arg
20
+ // roles (dbt: ref's model is the last arg) reads value + valueSpan straight off.
21
+ // - a `{% … %}` statement tag → "control" (if/for/set/macro structure).
22
+ // - any other expr (a bare name, a literal, arithmetic) or a `{# … #}` comment →
23
+ // "other".
28
24
  //
29
- // Never-wrong (global-constraints): a ref/source node is emitted only when its
30
- // required string args are actually present; a broken `{{ ref( }}` degrades to a
31
- // macro node (name = "ref") + the parse's positioned diagnostic, never a ref
32
- // node with a fabricated modelSpan and never a throw.
25
+ // Never-wrong (global-constraints): a string arg's `value` is a real literal only
26
+ // (a computed arg like `ref(var('x'))` has `value: null`, never fabricated); a
27
+ // broken `{{ ref( }}` degrades to a best-effort node + the parse's positioned
28
+ // diagnostic, never a throw.
33
29
  // ---------------------------------------------------------------------------
34
- import { ParserRuleContext, TerminalNode } from "antlr4ng";
30
+ import { ParserRuleContext, TerminalNode, Token as AntlrToken } from "antlr4ng";
35
31
  import { MinijinjaParser } from "../generated/minijinja/MinijinjaParser.js";
36
32
  import { CallExprContext, Endraw_tagContext, MemberExprContext, NameExprContext, Raw_tagContext, StmtContext, } from "../generated/minijinja/MinijinjaParser.js";
37
33
  import { endPosition } from "../ir/span.js";
38
- import { NO_OUTPUT_BUILTINS } from "./segment.js";
39
34
  // ---------------------------------------------------------------------------
40
35
  // Span helpers. The tokens here come from parse.ts's per-tag `ListTokenSource`
41
36
  // slice over the ONE whole-document tokenization (segment.ts's `tagTokens`), so
@@ -257,9 +252,9 @@ function argInfo(call) {
257
252
  const args = [];
258
253
  if (argList) {
259
254
  for (const arg of argList.arg()) {
260
- const span = spanOfNode(arg);
261
- if (span)
262
- args.push({ span });
255
+ const a = argOf(arg);
256
+ if (a)
257
+ args.push(a);
263
258
  }
264
259
  }
265
260
  let argsSpan;
@@ -271,6 +266,42 @@ function argInfo(call) {
271
266
  }
272
267
  return { args, ...(argsSpan ? { argsSpan } : {}) };
273
268
  }
269
+ /** The bare literal token types an argument can be (besides STRING): a consumer reads their text as
270
+ * the arg value. */
271
+ const LITERAL_TOKENS = new Set([
272
+ MinijinjaParser.INT,
273
+ MinijinjaParser.FLOAT,
274
+ MinijinjaParser.TRUE,
275
+ MinijinjaParser.FALSE,
276
+ MinijinjaParser.NONE,
277
+ ]);
278
+ /** One argument as `{value, span, valueSpan?}`. `span` is the WHOLE arg (incl. any `name=` and quotes).
279
+ * The literal VALUE + its quote-excluded `valueSpan` come from the value expression: for a kwarg
280
+ * (`model='x'`) that's the RHS, for a positional it's the arg itself. A STRING literal yields the
281
+ * stripped value + content span; a bare number/bool/none yields its text + its span; anything
282
+ * computed yields `value: null` (never fabricated). undefined only when the arg has no span at all. */
283
+ function argOf(arg) {
284
+ const span = spanOfNode(arg);
285
+ if (!span)
286
+ return undefined;
287
+ const valueCtx = isKwarg(arg) ? kwargValue(arg) : arg;
288
+ if (valueCtx) {
289
+ const str = directStringToken(valueCtx);
290
+ if (str)
291
+ return { value: stringValue(str), span, valueSpan: stringContentSpan(str) };
292
+ const v = valueCtx.start;
293
+ if (v && v === valueCtx.stop && LITERAL_TOKENS.has(v.type)) {
294
+ return { value: v.text ?? null, span, valueSpan: spanOfNode(valueCtx) };
295
+ }
296
+ }
297
+ return { value: null, span };
298
+ }
299
+ /** The value expression of a kwarg (`id = expr` → the `expr`), or undefined. It is the last child;
300
+ * the ASSIGN and id precede it. */
301
+ function kwargValue(arg) {
302
+ const last = arg.getChild(arg.getChildCount() - 1);
303
+ return last instanceof ParserRuleContext ? last : undefined;
304
+ }
274
305
  /**
275
306
  * Extract a CallExprContext into the reusable MacroCall fields — name/nameSpan +
276
307
  * optional package via `decomposeCallee`, args[] + argsSpan via `argInfo`. Returns
@@ -279,7 +310,7 @@ function argInfo(call) {
279
310
  * fabricated (never-wrong). The `{{ }}` macro node and the `{% %}` control tag both
280
311
  * build from this, so a call surfaces identically wherever it appears.
281
312
  */
282
- export function callToMacroCall(call) {
313
+ function callToMacroCall(call) {
283
314
  const callee = decomposeCallee(call);
284
315
  if (!callee || !callee.nameSpan)
285
316
  return undefined;
@@ -306,26 +337,159 @@ function macroNode(call, callee, tagSpan, tree) {
306
337
  if (mc)
307
338
  calls.push(mc);
308
339
  }
340
+ const callSpan = spanOfNode(call) ?? tagSpan;
309
341
  const mc = callToMacroCall(call);
310
342
  if (mc)
311
- return { kind: "macro", ...mc, tagSpan, calls };
343
+ return { kind: "call", ...mc, callSpan, tagSpan, calls };
312
344
  // Degenerate fallback: a callee with a name but no locatable span (a broken
313
345
  // tree). Preserve the pre-refactor node shape — nameSpan defaults to tagSpan,
314
346
  // args are still extracted. (Unreachable for a well-formed parsed call, where
315
347
  // the identifier token always has a span; kept for behavioral parity.)
316
348
  const { args, argsSpan } = argInfo(call);
317
349
  return {
318
- kind: "macro",
350
+ kind: "call",
319
351
  name: callee.name,
320
352
  nameSpan: tagSpan,
321
353
  ...(callee.packageName !== undefined ? { packageName: callee.packageName } : {}),
322
354
  ...(callee.packageSpan !== undefined ? { packageSpan: callee.packageSpan } : {}),
355
+ callSpan,
323
356
  tagSpan,
324
357
  ...(argsSpan ? { argsSpan } : {}),
325
358
  args,
326
359
  calls,
327
360
  };
328
361
  }
362
+ /** The tag's significant tokens: default channel, no EOF, no `{{`/`}}`/`{%` delimiters. */
363
+ function significantTokens(tokens) {
364
+ const P = MinijinjaParser;
365
+ return tokens.filter((t) => t.channel === AntlrToken.DEFAULT_CHANNEL &&
366
+ t.type !== AntlrToken.EOF &&
367
+ t.type !== P.EXPR_OPEN &&
368
+ t.type !== P.EXPR_CLOSE &&
369
+ t.type !== P.STMT_OPEN);
370
+ }
371
+ /** The INNERMOST still-open CALL paren in `sig` (the call being typed), or undefined when none is
372
+ * open. Every `(` is pushed (a call when the token before it is an `id` / `pkg.id`, else a grouping
373
+ * paren = null) and every `)` pops, so `outer(inner(` reports inner, a call after a keyword
374
+ * (`if is_incremental(`) is found past the leading words, and a grouping paren never masks the call
375
+ * it sits inside. */
376
+ function trailingOpenCall(sig) {
377
+ const P = MinijinjaParser;
378
+ const stack = [];
379
+ for (let i = 0; i < sig.length; i++) {
380
+ const t = sig[i];
381
+ if (t.type === P.LPAREN) {
382
+ const prev = sig[i - 1];
383
+ if (prev?.type === P.ID) {
384
+ const packageTok = sig[i - 2]?.type === P.DOT && sig[i - 3]?.type === P.ID ? sig[i - 3] : undefined;
385
+ stack.push({ nameTok: prev, ...(packageTok ? { packageTok } : {}), lparenIdx: i });
386
+ }
387
+ else {
388
+ stack.push(null); // a grouping paren, not a call
389
+ }
390
+ }
391
+ else if (t.type === P.RPAREN) {
392
+ stack.pop();
393
+ }
394
+ }
395
+ for (let k = stack.length - 1; k >= 0; k--) {
396
+ const e = stack[k];
397
+ if (e)
398
+ return e;
399
+ }
400
+ return undefined;
401
+ }
402
+ /** The args of an unclosed call: the significant tokens from just after its `(` (index `from`) to the
403
+ * end, split into args by top-level commas. Best-effort per `argFromTokens`. */
404
+ function argsFrom(sig, from) {
405
+ const P = MinijinjaParser;
406
+ const args = [];
407
+ let depth = 0;
408
+ let cur = [];
409
+ for (let i = from; i < sig.length; i++) {
410
+ const t = sig[i];
411
+ if (t.type === P.LPAREN) {
412
+ depth++;
413
+ cur.push(t);
414
+ }
415
+ else if (t.type === P.RPAREN) {
416
+ if (depth === 0)
417
+ break;
418
+ depth--;
419
+ cur.push(t);
420
+ }
421
+ else if (t.type === P.COMMA && depth === 0) {
422
+ if (cur.length > 0)
423
+ args.push(argFromTokens(cur));
424
+ cur = [];
425
+ }
426
+ else {
427
+ cur.push(t);
428
+ }
429
+ }
430
+ if (cur.length > 0)
431
+ args.push(argFromTokens(cur));
432
+ return args;
433
+ }
434
+ /** The reusable MacroCall fields of the innermost unclosed call in `tokens`, or undefined when none is
435
+ * open (a bare name / non-call expr). Shared by the expr-tag node recovery and the control-tag
436
+ * embedded-call recovery. Args are best-effort: a complete STRING is value + quote-excluded span; a
437
+ * partial or computed arg is `value: null` + its span (never fabricated). */
438
+ function openCall(tokens) {
439
+ const sig = significantTokens(tokens);
440
+ const open = trailingOpenCall(sig);
441
+ if (!open)
442
+ return undefined;
443
+ const lparen = sig[open.lparenIdx];
444
+ const last = sig[sig.length - 1];
445
+ return {
446
+ name: open.nameTok.text ?? "",
447
+ nameSpan: spanFromTokens(open.nameTok, open.nameTok),
448
+ ...(open.packageTok
449
+ ? { packageName: open.packageTok.text ?? "", packageSpan: spanFromTokens(open.packageTok, open.packageTok) }
450
+ : {}),
451
+ argsSpan: spanFromTokens(lparen, last),
452
+ args: argsFrom(sig, open.lparenIdx + 1),
453
+ };
454
+ }
455
+ /** An UNCLOSED / mid-typing expr-tag call (`{{ ref('cu`, `{{ outer(inner(`) recovered as a neutral
456
+ * "call" node flagged `incomplete: true`, so completion can place the caret's slot. Reports the
457
+ * INNERMOST open call. Undefined when there's no open `id (` (a bare name / non-call expr stays
458
+ * "other"). */
459
+ function incompleteCall(tokens, tagSpan) {
460
+ const c = openCall(tokens);
461
+ if (!c || !c.argsSpan)
462
+ return undefined;
463
+ const from = c.packageSpan ?? c.nameSpan;
464
+ return {
465
+ kind: "call",
466
+ name: c.name,
467
+ nameSpan: c.nameSpan,
468
+ ...(c.packageName !== undefined ? { packageName: c.packageName, packageSpan: c.packageSpan } : {}),
469
+ callSpan: {
470
+ start: from.start,
471
+ end: c.argsSpan.end,
472
+ line: from.line,
473
+ column: from.column,
474
+ endLine: c.argsSpan.endLine,
475
+ endColumn: c.argsSpan.endColumn,
476
+ },
477
+ tagSpan,
478
+ argsSpan: c.argsSpan,
479
+ args: c.args,
480
+ calls: [],
481
+ incomplete: true,
482
+ };
483
+ }
484
+ /** One arg of an incomplete call from its raw tokens: a lone complete STRING → value + quote-excluded
485
+ * span; anything else (a partial/unterminated string, a computed expression) → value null + span. */
486
+ function argFromTokens(toks) {
487
+ const span = spanFromTokens(toks[0], toks[toks.length - 1]);
488
+ if (toks.length === 1 && toks[0].type === MinijinjaParser.STRING) {
489
+ return { value: stringValue(toks[0]), span, valueSpan: stringContentSpan(toks[0]) };
490
+ }
491
+ return { value: null, span };
492
+ }
329
493
  /** Keywords whose stmt declares a name we surface (set target / macro name / for loop var). */
330
494
  const NAME_DECLARING = new Set(["set", "macro", "for"]);
331
495
  /**
@@ -371,7 +535,7 @@ function controlNode(tree, tagSpan) {
371
535
  * offset/anchor composition anywhere in this file). Returns undefined only for an
372
536
  * empty/degenerate tree (never throws).
373
537
  */
374
- export function tagNodesOf(seg, tree) {
538
+ export function tagNodesOf(seg, tree, tokens = []) {
375
539
  // tagSpan is the whole tag including delimiters — the segment bounds are exact
376
540
  // (they cover `{{ … }}` / `{% … %}` / `{# … #}` and any `-` whitespace control);
377
541
  // its line/column come from the tree's own start token (the tag's OPEN token).
@@ -399,70 +563,26 @@ export function tagNodesOf(seg, tree) {
399
563
  const selfKeyword = selfContainedKeyword(tree);
400
564
  if (selfKeyword)
401
565
  return { kind: "control", tagSpan, keyword: selfKeyword, calls: [] };
402
- return controlNode(tree, tagSpan);
566
+ const node = controlNode(tree, tagSpan);
567
+ // Recover a trailing UNCLOSED call being typed (`{% if is_incremental(`), which the tree parse
568
+ // could not build, and add it to `calls` so completion can place the caret's slot inside it.
569
+ const partial = openCall(tokens);
570
+ return partial && node.kind === "control" ? { ...node, calls: [...node.calls, partial] } : node;
403
571
  }
404
572
  if (seg.tagKind === "comment")
405
573
  return { kind: "other", tagSpan };
406
574
  // Expr tag: classify by the leading call.
407
575
  const call = findTopCall(tree);
408
- if (!call)
409
- return { kind: "other", tagSpan };
576
+ if (!call) {
577
+ // No complete call node: recover an UNCLOSED/mid-typing call from the raw tokens
578
+ // (`{{ ref('cu`) so completion can place the caret's slot. A bare non-call expr stays "other".
579
+ return incompleteCall(tokens, tagSpan) ?? { kind: "other", tagSpan };
580
+ }
410
581
  const callee = decomposeCallee(call);
411
582
  if (!callee)
412
583
  return { kind: "other", tagSpan };
413
- const leading = callee.leading;
414
- const bare = callee.packageName === undefined;
415
- // Bare special forms (ref/source/var/env_var never take a package qualifier).
416
- if (bare && leading === "ref") {
417
- // The model is the LAST positional arg (dbt: ref('pkg','model')), and only
418
- // when THAT arg is a direct string literal — a computed target
419
- // (`ref(var('x'))`) does not fabricate a model.
420
- const pos = positionalArgs(call.arg_list());
421
- const modelArg = pos.at(-1);
422
- const modelTok = modelArg ? directStringToken(modelArg) : undefined;
423
- if (modelTok) {
424
- const callSpan = spanOfNode(call) ?? tagSpan;
425
- return {
426
- kind: "ref",
427
- model: stringValue(modelTok),
428
- modelSpan: stringContentSpan(modelTok),
429
- callSpan,
430
- tagSpan,
431
- };
432
- }
433
- // computed / broken / argless ref → macro fallback (never a fabricated model).
434
- return macroNode(call, callee, tagSpan, tree);
435
- }
436
- if (bare && leading === "source") {
437
- // source('name', 'table') — both must be DIRECT string literals; a computed
438
- // arg does not fabricate a name/table.
439
- const pos = positionalArgs(call.arg_list());
440
- const srcTok = pos[0] ? directStringToken(pos[0]) : undefined;
441
- const tblTok = pos[1] ? directStringToken(pos[1]) : undefined;
442
- if (srcTok && tblTok) {
443
- const callSpan = spanOfNode(call) ?? tagSpan;
444
- return {
445
- kind: "source",
446
- sourceName: stringValue(srcTok),
447
- tableName: stringValue(tblTok),
448
- sourceNameSpan: stringContentSpan(srcTok),
449
- tableNameSpan: stringContentSpan(tblTok),
450
- callSpan,
451
- tagSpan,
452
- };
453
- }
454
- return macroNode(call, callee, tagSpan, tree);
455
- }
456
- if (bare && leading === "var")
457
- return { kind: "var", tagSpan };
458
- if (bare && leading === "env_var")
459
- return { kind: "env_var", tagSpan };
460
- // No-output builtins (config/docs/print/log/return/exceptions) — config is its
461
- // own kind; the rest map to "other". Applies to the leading name whether bare
462
- // (`config(...)`) or member (`exceptions.raise_compiler_error(...)`).
463
- if (NO_OUTPUT_BUILTINS.has(leading)) {
464
- return leading === "config" ? { kind: "config", tagSpan } : { kind: "other", tagSpan };
465
- }
466
- // pkg.macro(...) or a bare unknown call → macro.
584
+ // Every expression-call tag is one neutral CALL node. ref/source/config/var/env_var are just
585
+ // callees distinguished by `name`; the provider (DbtTemplateProvider) interprets what each call
586
+ // MEANS (relation / no-output / scalar). The tag AST carries no dbt vocabulary.
467
587
  return macroNode(call, callee, tagSpan, tree);
468
588
  }
@@ -0,0 +1,2 @@
1
+ import type { DialectBehavior } from "../dialect-behavior/behavior.js";
2
+ export declare const mysqlBehavior: DialectBehavior;
@@ -0,0 +1,21 @@
1
+ // The mysql DialectBehavior: everything the semantic layer needs for mysql, assembled from
2
+ // this folder's own pieces. The registry wires it; nothing here reaches a central per-dialect table.
3
+ import { mysqlLiteral, mysqlParseType, MYSQL_FUNCTION_RETURNS } from "./infer.js";
4
+ import { displayName, fold, foldTableName, matchesSourceKey } from "./fold.js";
5
+ import { likePatternToRegExp } from "../scope/like-pattern.js";
6
+ import { SIGNATURES } from "../signature/signatures.js";
7
+ import { acceptsFor } from "../dialect-behavior/coerce-rules.js";
8
+ export const mysqlBehavior = {
9
+ fold,
10
+ displayName,
11
+ foldTableName,
12
+ matchesSourceKey,
13
+ likeMatch: (pattern, value) => likePatternToRegExp(pattern).test(value),
14
+ literal: mysqlLiteral,
15
+ parseType: mysqlParseType,
16
+ functions: MYSQL_FUNCTION_RETURNS,
17
+ division: "decimal",
18
+ signatures: SIGNATURES.mysql,
19
+ // STR_TO_NUM=true, BOOL_NUM=true: mysql implicitly coerces both string->number and bool<->number.
20
+ accepts: (argType, paramText) => acceptsFor(mysqlParseType, true, true, argType, paramText),
21
+ };
@@ -0,0 +1,8 @@
1
+ import { type FoldRule, type IdentKind } from "../ident/fold.js";
2
+ export declare const MYSQL_FOLD_RULE: FoldRule;
3
+ /** Fold an identifier to its MySQL identity key. */
4
+ export declare function fold(raw: string, kind?: IdentKind): string;
5
+ /** Presentation twin: strip delimiters, no case change. */
6
+ export declare function displayName(raw: string): string;
7
+ export declare function foldTableName(parts: string[]): string[];
8
+ export declare function matchesSourceKey(key: string, rawPart: string): boolean;
@@ -0,0 +1,49 @@
1
+ // MySQL identifier folding. The FoldRule plus its bound engine, colocated here because BOTH the
2
+ // upstream lower() and the downstream DialectBehavior need it (the fold rule is the one dialect concern
3
+ // used at two stages).
4
+ //
5
+ // dev.mysql.com/doc/refman/8.4/en/identifier-case-sensitivity.html — verified live. This module's
6
+ // fold rule is per-DIALECT, not per-identifier-KIND (only BigQuery's `tableCase` override draws
7
+ // that distinction), so one rule must cover both halves of a genuinely split MySQL reality:
8
+ // "Partition, subpartition, column, index, stored routine, event, and resource group names are
9
+ // not case-sensitive on any platform, nor are column aliases" — unconditional, so this module
10
+ // folds unquoted AND backtick-quoted identifiers to lower for equality (column/alias/CTE/field
11
+ // names, the identifier kinds this module actually threads through scope/qualify/infer today).
12
+ // KNOWN LIMITATION (documented here, not silently dropped): table/database names are genuinely
13
+ // platform/`lower_case_table_names`-dependent — "case sensitivity of the underlying operating
14
+ // system plays a part": case-SENSITIVE by default on Unix (lower_case_table_names=0), case-
15
+ // INSENSITIVE by default on Windows (=1) and macOS (=2), and the variable "can only be
16
+ // configured when initializing the server" (not discoverable from SQL text alone). A static
17
+ // analyzer with no server/OS context cannot resolve this per-installation choice; this module
18
+ // applies the same lower-fold to table identifiers too (foldTableName's kind:"table" path, since
19
+ // no `tableCase` override is set here) — correct for the Windows/macOS default and for Unix
20
+ // installs whose tables happen to be written in a single consistent case (the common case), WRONG
21
+ // only when a Unix-collation database genuinely holds two tables differing only in case (e.g.
22
+ // `Orders` and `orders` as distinct tables) — a real but narrow misresolution, called out here
23
+ // rather than left for a caller to discover by surprise. Identifier quote char is the backtick by
24
+ // default (ANSI_QUOTES mode, which repurposes `"` as the identifier quote and `` ` `` stops being
25
+ // special, is a session/server SQL-mode setting not visible in the SQL text either — out of
26
+ // scope for the same reason as lower_case_table_names). Doubled-quote escape: "To include a quote
27
+ // character within an identifier, quote the identifier and double the quote character" (example:
28
+ // `` `a``b` `` → `` a`b ``).
29
+ import { displayWith, foldWith } from "../ident/fold.js";
30
+ const BACKTICK = ["`", "`"];
31
+ export const MYSQL_FOLD_RULE = {
32
+ delimiters: [BACKTICK],
33
+ unquoted: "lower",
34
+ quoted: "lower",
35
+ };
36
+ /** Fold an identifier to its MySQL identity key. */
37
+ export function fold(raw, kind = "other") {
38
+ return foldWith(MYSQL_FOLD_RULE, raw, kind);
39
+ }
40
+ /** Presentation twin: strip delimiters, no case change. */
41
+ export function displayName(raw) {
42
+ return displayWith(MYSQL_FOLD_RULE, raw);
43
+ }
44
+ export function foldTableName(parts) {
45
+ return parts.map((p) => fold(p, "table"));
46
+ }
47
+ export function matchesSourceKey(key, rawPart) {
48
+ return key === fold(rawPart) || key === fold(rawPart, "table");
49
+ }
@@ -0,0 +1,7 @@
1
+ import { lower } from "./lower.js";
2
+ import { parseMysql } from "./parse.js";
3
+ export declare const mysql: {
4
+ parse: typeof parseMysql;
5
+ lower: typeof lower;
6
+ behavior: import("../dialect-behavior/behavior.js").DialectBehavior;
7
+ };
@@ -0,0 +1,10 @@
1
+ // The complete mysql dialect module: parse + lower (front end) and behavior (semantic knowledge).
2
+ // The registry wires this; to understand everything sqllens does for mysql, read this folder.
3
+ import { mysqlBehavior } from "./behavior.js";
4
+ import { lower } from "./lower.js";
5
+ import { parseMysql } from "./parse.js";
6
+ export const mysql = {
7
+ parse: parseMysql,
8
+ lower,
9
+ behavior: mysqlBehavior,
10
+ };
@@ -1,5 +1,5 @@
1
- import { type Type } from "./types.js";
2
- import type { FnRule } from "./functions.js";
1
+ import { type Type } from "../infer/types.js";
2
+ import type { FnRule } from "../infer/functions.js";
3
3
  export declare const MYSQL_ALIASES: Record<string, string>;
4
4
  export declare function mysqlParseType(text: string): Type;
5
5
  /** MySQL literal forms (dev.mysql.com/doc/refman/8.4/en/literals.html). Numerics split on MySQL's
@@ -1,4 +1,5 @@
1
- import { parseType, scalar, UNKNOWN } from "./types.js";
1
+ import { parseType, scalar, UNKNOWN } from "../infer/types.js";
2
+ import { fold } from "./fold.js";
2
3
  // ---------------------------------------------------------------------------
3
4
  // MySQL inference knowledge — function return types, literal forms, and scalar-type aliases,
4
5
  // from the MySQL 8.4 Reference Manual (dev.mysql.com/doc/refman/8.4/en/). Same contract as every
@@ -58,7 +59,7 @@ export const MYSQL_ALIASES = {
58
59
  unsignedint: "bigint",
59
60
  };
60
61
  export function mysqlParseType(text) {
61
- return parseType(text, MYSQL_ALIASES, "mysql");
62
+ return parseType(text, MYSQL_ALIASES, fold);
62
63
  }
63
64
  const S = scalar("string");
64
65
  const BIG = scalar("bigint");
@@ -0,0 +1,6 @@
1
+ import type { FnSignature } from "../signature/signatures.js";
2
+ /** The merged function-signature table for mysql: curated overrides folded over the harvested
3
+ * doc-derived long tail (overrides win by key, replacing the whole overload set), keyed by
4
+ * lowercased name. Each name maps to an ORDERED overload set - a name with one documented shape
5
+ * is a one-element array. `origin` says which layer produced the set. */
6
+ export declare const MYSQL_SIGNATURES: Record<string, FnSignature[]>;