@514labs/moose-lsp 1.3.12000161 → 1.4.1

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.
@@ -1,5 +1,5 @@
1
1
  import ts from 'typescript';
2
- import { TextEdit } from 'vscode-languageserver/node';
2
+ import { Diagnostic, CodeAction, TextEdit } from 'vscode-languageserver/node';
3
3
  import { SqlLocation } from './sqlLocations.js';
4
4
 
5
5
  /**
@@ -23,5 +23,10 @@ declare function createFormatSqlEdit(sourceFile: ts.SourceFile, node: ts.TaggedT
23
23
  * Finds a TaggedTemplateExpression by its location ID.
24
24
  */
25
25
  declare function findTemplateNodeById(sourceFile: ts.SourceFile, locationId: string): ts.TaggedTemplateExpression | undefined;
26
+ /**
27
+ * Creates quick-fix code actions for deprecated bare `sql` tag diagnostics.
28
+ * Offers "Convert to sql.statement" and "Convert to sql.fragment".
29
+ */
30
+ declare function createDeprecationCodeActions(uri: string, diagnostics: Diagnostic[]): CodeAction[];
26
31
 
27
- export { createFormatSqlEdit, extractOriginalExpressions, findSqlTemplateAtPosition, findTemplateNodeById };
32
+ export { createDeprecationCodeActions, createFormatSqlEdit, extractOriginalExpressions, findSqlTemplateAtPosition, findTemplateNodeById };
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var codeActions_exports = {};
30
30
  __export(codeActions_exports, {
31
+ createDeprecationCodeActions: () => createDeprecationCodeActions,
31
32
  createFormatSqlEdit: () => createFormatSqlEdit,
32
33
  extractOriginalExpressions: () => extractOriginalExpressions,
33
34
  findSqlTemplateAtPosition: () => findSqlTemplateAtPosition,
@@ -35,6 +36,7 @@ __export(codeActions_exports, {
35
36
  });
36
37
  module.exports = __toCommonJS(codeActions_exports);
37
38
  var import_typescript = __toESM(require("typescript"));
39
+ var import_node = require("vscode-languageserver/node");
38
40
  var import_formatting = require("./formatting");
39
41
  function findSqlTemplateAtPosition(locations, line, character) {
40
42
  const cursorLine = line + 1;
@@ -142,17 +144,21 @@ function createFormatSqlEdit(sourceFile, node) {
142
144
  }
143
145
  function findTemplateNodeById(sourceFile, locationId) {
144
146
  let targetNode;
147
+ function isSqlTag(tag) {
148
+ if (import_typescript.default.isIdentifier(tag) && tag.text === "sql") return true;
149
+ if (import_typescript.default.isPropertyAccessExpression(tag) && import_typescript.default.isIdentifier(tag.expression) && tag.expression.text === "sql" && (tag.name.text === "statement" || tag.name.text === "fragment")) {
150
+ return true;
151
+ }
152
+ return false;
153
+ }
145
154
  function visit(node) {
146
- if (import_typescript.default.isTaggedTemplateExpression(node)) {
147
- const tag = node.tag;
148
- if (import_typescript.default.isIdentifier(tag) && tag.text === "sql") {
149
- const start = sourceFile.getLineAndCharacterOfPosition(
150
- node.template.getStart()
151
- );
152
- const id = `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`;
153
- if (id === locationId) {
154
- targetNode = node;
155
- }
155
+ if (import_typescript.default.isTaggedTemplateExpression(node) && isSqlTag(node.tag)) {
156
+ const start = sourceFile.getLineAndCharacterOfPosition(
157
+ node.template.getStart()
158
+ );
159
+ const id = `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`;
160
+ if (id === locationId) {
161
+ targetNode = node;
156
162
  }
157
163
  }
158
164
  if (!targetNode) {
@@ -162,8 +168,49 @@ function findTemplateNodeById(sourceFile, locationId) {
162
168
  visit(sourceFile);
163
169
  return targetNode;
164
170
  }
171
+ function createDeprecationCodeActions(uri, diagnostics) {
172
+ const actions = [];
173
+ for (const diagnostic of diagnostics) {
174
+ const data = diagnostic.data;
175
+ if (data?.type !== "deprecated-sql-tag") continue;
176
+ actions.push(
177
+ {
178
+ title: "Convert to 'sql.statement'",
179
+ kind: import_node.CodeActionKind.QuickFix,
180
+ diagnostics: [diagnostic],
181
+ edit: {
182
+ changes: {
183
+ [uri]: [
184
+ {
185
+ range: diagnostic.range,
186
+ newText: "sql.statement"
187
+ }
188
+ ]
189
+ }
190
+ }
191
+ },
192
+ {
193
+ title: "Convert to 'sql.fragment'",
194
+ kind: import_node.CodeActionKind.QuickFix,
195
+ diagnostics: [diagnostic],
196
+ edit: {
197
+ changes: {
198
+ [uri]: [
199
+ {
200
+ range: diagnostic.range,
201
+ newText: "sql.fragment"
202
+ }
203
+ ]
204
+ }
205
+ }
206
+ }
207
+ );
208
+ }
209
+ return actions;
210
+ }
165
211
  // Annotate the CommonJS export names for ESM import in node:
166
212
  0 && (module.exports = {
213
+ createDeprecationCodeActions,
167
214
  createFormatSqlEdit,
168
215
  extractOriginalExpressions,
169
216
  findSqlTemplateAtPosition,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codeActions.ts"],"sourcesContent":["import ts from 'typescript';\nimport type { TextEdit } from 'vscode-languageserver/node';\nimport { formatSqlTemplate } from './formatting';\nimport type { SqlLocation } from './sqlLocations';\n\n/**\n * Finds the SQL template that contains the given cursor position.\n * @param locations - Array of SQL template locations\n * @param line - 0-indexed cursor line\n * @param character - 0-indexed cursor column\n * @returns The SqlLocation containing the cursor, or undefined\n */\nexport function findSqlTemplateAtPosition(\n locations: SqlLocation[],\n line: number,\n character: number,\n): SqlLocation | undefined {\n // Convert to 1-indexed for comparison with SqlLocation\n const cursorLine = line + 1;\n const cursorColumn = character + 1;\n\n for (const loc of locations) {\n // Check if cursor is within the template bounds\n const afterStart =\n cursorLine > loc.line ||\n (cursorLine === loc.line && cursorColumn >= loc.column);\n\n const beforeEnd =\n cursorLine < loc.endLine ||\n (cursorLine === loc.endLine && cursorColumn <= loc.endColumn);\n\n if (afterStart && beforeEnd) {\n return loc;\n }\n }\n\n return undefined;\n}\n\n/**\n * Extract original ${expr} expressions from a template literal.\n * Returns array of expression source texts like ['${col}', '${table}'].\n */\nexport function extractOriginalExpressions(\n template: ts.TemplateLiteral,\n): string[] {\n if (ts.isNoSubstitutionTemplateLiteral(template)) {\n return [];\n }\n\n const expressions: string[] = [];\n for (const span of template.templateSpans) {\n const exprText = span.expression.getText();\n expressions.push(`\\${${exprText}}`);\n }\n return expressions;\n}\n\n/**\n * Extract template text with ${...} placeholders.\n */\nfunction extractTemplateText(template: ts.TemplateLiteral): string {\n if (ts.isNoSubstitutionTemplateLiteral(template)) {\n return template.text;\n }\n\n let text = template.head.text;\n for (const span of template.templateSpans) {\n text += `\\${...}${span.literal.text}`;\n }\n return text;\n}\n\n/**\n * Detects the indentation used in the original template.\n * Returns the indentation string (spaces/tabs) and whether it starts with a newline.\n */\nfunction detectTemplateIndentation(templateText: string): {\n indent: string;\n hasLeadingNewline: boolean;\n hasTrailingNewline: boolean;\n trailingIndent: string;\n} {\n const hasLeadingNewline = templateText.startsWith('\\n');\n const hasTrailingNewline =\n templateText.endsWith('\\n') || /\\n\\s*$/.test(templateText);\n\n // Find indentation from the first non-empty line after the opening\n let indent = '';\n if (hasLeadingNewline) {\n const lines = templateText.split('\\n');\n // Find first line with actual content (skip empty lines)\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().length > 0) {\n const match = line.match(/^(\\s*)/);\n if (match) {\n indent = match[1];\n }\n break;\n }\n }\n }\n\n // Find trailing indentation (indentation of the closing backtick line)\n let trailingIndent = '';\n if (hasTrailingNewline) {\n const match = templateText.match(/\\n(\\s*)$/);\n if (match) {\n trailingIndent = match[1];\n }\n }\n\n return { indent, hasLeadingNewline, hasTrailingNewline, trailingIndent };\n}\n\n/**\n * Applies indentation to formatted SQL, preserving the original template structure.\n */\nfunction applyIndentation(\n formatted: string,\n indent: string,\n hasLeadingNewline: boolean,\n hasTrailingNewline: boolean,\n trailingIndent: string,\n): string {\n // Split formatted SQL into lines and apply indentation\n const lines = formatted.split('\\n');\n const indentedLines = lines.map((line) =>\n line.length > 0 ? indent + line : line,\n );\n\n let result = indentedLines.join('\\n');\n\n // Add leading newline if original had one\n if (hasLeadingNewline) {\n result = `\\n${result}`;\n }\n\n // Add trailing newline and indentation if original had it\n if (hasTrailingNewline) {\n result = `${result}\\n${trailingIndent}`;\n }\n\n return result;\n}\n\n/**\n * Creates the text edit for formatting a SQL template.\n */\nexport function createFormatSqlEdit(\n sourceFile: ts.SourceFile,\n node: ts.TaggedTemplateExpression,\n): TextEdit | undefined {\n const originalExprs = extractOriginalExpressions(node.template);\n const templateText = extractTemplateText(node.template);\n\n const result = formatSqlTemplate(templateText, originalExprs);\n if (!result.success || !result.formatted) {\n return undefined;\n }\n\n // Detect indentation from original template\n const { indent, hasLeadingNewline, hasTrailingNewline, trailingIndent } =\n detectTemplateIndentation(templateText);\n\n // Apply indentation to formatted SQL\n const formattedWithIndent = applyIndentation(\n result.formatted,\n indent,\n hasLeadingNewline,\n hasTrailingNewline,\n trailingIndent,\n );\n\n // Get positions inside the backticks\n const templateStart = sourceFile.getLineAndCharacterOfPosition(\n node.template.getStart() + 1,\n );\n const templateEnd = sourceFile.getLineAndCharacterOfPosition(\n node.template.getEnd() - 1,\n );\n\n return {\n range: {\n start: { line: templateStart.line, character: templateStart.character },\n end: { line: templateEnd.line, character: templateEnd.character },\n },\n newText: formattedWithIndent,\n };\n}\n\n/**\n * Finds a TaggedTemplateExpression by its location ID.\n */\nexport function findTemplateNodeById(\n sourceFile: ts.SourceFile,\n locationId: string,\n): ts.TaggedTemplateExpression | undefined {\n let targetNode: ts.TaggedTemplateExpression | undefined;\n\n function visit(node: ts.Node): void {\n if (ts.isTaggedTemplateExpression(node)) {\n const tag = node.tag;\n if (ts.isIdentifier(tag) && tag.text === 'sql') {\n const start = sourceFile.getLineAndCharacterOfPosition(\n node.template.getStart(),\n );\n const id = `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`;\n if (id === locationId) {\n targetNode = node;\n }\n }\n }\n if (!targetNode) {\n ts.forEachChild(node, visit);\n }\n }\n\n visit(sourceFile);\n return targetNode;\n}\n"],"mappings":"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","names":["ts"]}
1
+ {"version":3,"sources":["../src/codeActions.ts"],"sourcesContent":["import ts from 'typescript';\nimport {\n type CodeAction,\n CodeActionKind,\n type Diagnostic,\n type TextEdit,\n} from 'vscode-languageserver/node';\nimport { formatSqlTemplate } from './formatting';\nimport type { SqlLocation } from './sqlLocations';\n\n/**\n * Finds the SQL template that contains the given cursor position.\n * @param locations - Array of SQL template locations\n * @param line - 0-indexed cursor line\n * @param character - 0-indexed cursor column\n * @returns The SqlLocation containing the cursor, or undefined\n */\nexport function findSqlTemplateAtPosition(\n locations: SqlLocation[],\n line: number,\n character: number,\n): SqlLocation | undefined {\n // Convert to 1-indexed for comparison with SqlLocation\n const cursorLine = line + 1;\n const cursorColumn = character + 1;\n\n for (const loc of locations) {\n // Check if cursor is within the template bounds\n const afterStart =\n cursorLine > loc.line ||\n (cursorLine === loc.line && cursorColumn >= loc.column);\n\n const beforeEnd =\n cursorLine < loc.endLine ||\n (cursorLine === loc.endLine && cursorColumn <= loc.endColumn);\n\n if (afterStart && beforeEnd) {\n return loc;\n }\n }\n\n return undefined;\n}\n\n/**\n * Extract original ${expr} expressions from a template literal.\n * Returns array of expression source texts like ['${col}', '${table}'].\n */\nexport function extractOriginalExpressions(\n template: ts.TemplateLiteral,\n): string[] {\n if (ts.isNoSubstitutionTemplateLiteral(template)) {\n return [];\n }\n\n const expressions: string[] = [];\n for (const span of template.templateSpans) {\n const exprText = span.expression.getText();\n expressions.push(`\\${${exprText}}`);\n }\n return expressions;\n}\n\n/**\n * Extract template text with ${...} placeholders.\n */\nfunction extractTemplateText(template: ts.TemplateLiteral): string {\n if (ts.isNoSubstitutionTemplateLiteral(template)) {\n return template.text;\n }\n\n let text = template.head.text;\n for (const span of template.templateSpans) {\n text += `\\${...}${span.literal.text}`;\n }\n return text;\n}\n\n/**\n * Detects the indentation used in the original template.\n * Returns the indentation string (spaces/tabs) and whether it starts with a newline.\n */\nfunction detectTemplateIndentation(templateText: string): {\n indent: string;\n hasLeadingNewline: boolean;\n hasTrailingNewline: boolean;\n trailingIndent: string;\n} {\n const hasLeadingNewline = templateText.startsWith('\\n');\n const hasTrailingNewline =\n templateText.endsWith('\\n') || /\\n\\s*$/.test(templateText);\n\n // Find indentation from the first non-empty line after the opening\n let indent = '';\n if (hasLeadingNewline) {\n const lines = templateText.split('\\n');\n // Find first line with actual content (skip empty lines)\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().length > 0) {\n const match = line.match(/^(\\s*)/);\n if (match) {\n indent = match[1];\n }\n break;\n }\n }\n }\n\n // Find trailing indentation (indentation of the closing backtick line)\n let trailingIndent = '';\n if (hasTrailingNewline) {\n const match = templateText.match(/\\n(\\s*)$/);\n if (match) {\n trailingIndent = match[1];\n }\n }\n\n return { indent, hasLeadingNewline, hasTrailingNewline, trailingIndent };\n}\n\n/**\n * Applies indentation to formatted SQL, preserving the original template structure.\n */\nfunction applyIndentation(\n formatted: string,\n indent: string,\n hasLeadingNewline: boolean,\n hasTrailingNewline: boolean,\n trailingIndent: string,\n): string {\n // Split formatted SQL into lines and apply indentation\n const lines = formatted.split('\\n');\n const indentedLines = lines.map((line) =>\n line.length > 0 ? indent + line : line,\n );\n\n let result = indentedLines.join('\\n');\n\n // Add leading newline if original had one\n if (hasLeadingNewline) {\n result = `\\n${result}`;\n }\n\n // Add trailing newline and indentation if original had it\n if (hasTrailingNewline) {\n result = `${result}\\n${trailingIndent}`;\n }\n\n return result;\n}\n\n/**\n * Creates the text edit for formatting a SQL template.\n */\nexport function createFormatSqlEdit(\n sourceFile: ts.SourceFile,\n node: ts.TaggedTemplateExpression,\n): TextEdit | undefined {\n const originalExprs = extractOriginalExpressions(node.template);\n const templateText = extractTemplateText(node.template);\n\n const result = formatSqlTemplate(templateText, originalExprs);\n if (!result.success || !result.formatted) {\n return undefined;\n }\n\n // Detect indentation from original template\n const { indent, hasLeadingNewline, hasTrailingNewline, trailingIndent } =\n detectTemplateIndentation(templateText);\n\n // Apply indentation to formatted SQL\n const formattedWithIndent = applyIndentation(\n result.formatted,\n indent,\n hasLeadingNewline,\n hasTrailingNewline,\n trailingIndent,\n );\n\n // Get positions inside the backticks\n const templateStart = sourceFile.getLineAndCharacterOfPosition(\n node.template.getStart() + 1,\n );\n const templateEnd = sourceFile.getLineAndCharacterOfPosition(\n node.template.getEnd() - 1,\n );\n\n return {\n range: {\n start: { line: templateStart.line, character: templateStart.character },\n end: { line: templateEnd.line, character: templateEnd.character },\n },\n newText: formattedWithIndent,\n };\n}\n\n/**\n * Finds a TaggedTemplateExpression by its location ID.\n */\nexport function findTemplateNodeById(\n sourceFile: ts.SourceFile,\n locationId: string,\n): ts.TaggedTemplateExpression | undefined {\n let targetNode: ts.TaggedTemplateExpression | undefined;\n\n function isSqlTag(tag: ts.Expression): boolean {\n if (ts.isIdentifier(tag) && tag.text === 'sql') return true;\n if (\n ts.isPropertyAccessExpression(tag) &&\n ts.isIdentifier(tag.expression) &&\n tag.expression.text === 'sql' &&\n (tag.name.text === 'statement' || tag.name.text === 'fragment')\n ) {\n return true;\n }\n return false;\n }\n\n function visit(node: ts.Node): void {\n if (ts.isTaggedTemplateExpression(node) && isSqlTag(node.tag)) {\n const start = sourceFile.getLineAndCharacterOfPosition(\n node.template.getStart(),\n );\n const id = `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`;\n if (id === locationId) {\n targetNode = node;\n }\n }\n if (!targetNode) {\n ts.forEachChild(node, visit);\n }\n }\n\n visit(sourceFile);\n return targetNode;\n}\n\n/**\n * Creates quick-fix code actions for deprecated bare `sql` tag diagnostics.\n * Offers \"Convert to sql.statement\" and \"Convert to sql.fragment\".\n */\nexport function createDeprecationCodeActions(\n uri: string,\n diagnostics: Diagnostic[],\n): CodeAction[] {\n const actions: CodeAction[] = [];\n\n for (const diagnostic of diagnostics) {\n const data = diagnostic.data as { type?: string } | undefined;\n if (data?.type !== 'deprecated-sql-tag') continue;\n\n actions.push(\n {\n title: \"Convert to 'sql.statement'\",\n kind: CodeActionKind.QuickFix,\n diagnostics: [diagnostic],\n edit: {\n changes: {\n [uri]: [\n {\n range: diagnostic.range,\n newText: 'sql.statement',\n },\n ],\n },\n },\n },\n {\n title: \"Convert to 'sql.fragment'\",\n kind: CodeActionKind.QuickFix,\n diagnostics: [diagnostic],\n edit: {\n changes: {\n [uri]: [\n {\n range: diagnostic.range,\n newText: 'sql.fragment',\n },\n ],\n },\n },\n },\n );\n }\n\n return actions;\n}\n"],"mappings":"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","names":["ts"]}
@@ -24,6 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  var import_node_assert = __toESM(require("node:assert"));
25
25
  var import_node_test = require("node:test");
26
26
  var import_typescript = __toESM(require("typescript"));
27
+ var import_node = require("vscode-languageserver/node");
27
28
  var import_codeActions = require("./codeActions");
28
29
  (0, import_node_test.test)("findSqlTemplateAtPosition Tests", async (t) => {
29
30
  const locations = [
@@ -34,7 +35,11 @@ var import_codeActions = require("./codeActions");
34
35
  column: 10,
35
36
  endLine: 5,
36
37
  endColumn: 50,
37
- templateText: "SELECT * FROM users"
38
+ templateText: "SELECT * FROM users",
39
+ tagKind: "bare",
40
+ tagLine: 1,
41
+ tagColumn: 1,
42
+ tagEndColumn: 4
38
43
  },
39
44
  {
40
45
  id: "test.ts:10:5",
@@ -43,7 +48,11 @@ var import_codeActions = require("./codeActions");
43
48
  column: 5,
44
49
  endLine: 15,
45
50
  endColumn: 10,
46
- templateText: "SELECT ${...} FROM ${...}"
51
+ templateText: "SELECT ${...} FROM ${...}",
52
+ tagKind: "bare",
53
+ tagLine: 1,
54
+ tagColumn: 1,
55
+ tagEndColumn: 4
47
56
  }
48
57
  ];
49
58
  await t.test("finds template when cursor is inside", () => {
@@ -203,4 +212,57 @@ function createSourceFileWithSqlTemplate(code) {
203
212
  );
204
213
  });
205
214
  });
215
+ (0, import_node_test.test)("createDeprecationCodeActions Tests", async (t) => {
216
+ await t.test(
217
+ "returns two code actions for deprecated-sql-tag diagnostic",
218
+ () => {
219
+ const diagnostic = {
220
+ range: {
221
+ start: { line: 0, character: 10 },
222
+ end: { line: 0, character: 13 }
223
+ },
224
+ severity: 4,
225
+ // Hint
226
+ tags: [2],
227
+ // Deprecated
228
+ message: "The 'sql' tag is deprecated...",
229
+ source: "moose-sql",
230
+ data: { type: "deprecated-sql-tag" }
231
+ };
232
+ const uri = "file:///project/test.ts";
233
+ const actions = (0, import_codeActions.createDeprecationCodeActions)(uri, [diagnostic]);
234
+ import_node_assert.default.strictEqual(actions.length, 2);
235
+ import_node_assert.default.ok(actions[0].title.includes("sql.statement"));
236
+ import_node_assert.default.strictEqual(actions[0].kind, import_node.CodeActionKind.QuickFix);
237
+ const changes0 = actions[0].edit?.changes?.[uri];
238
+ import_node_assert.default.ok(changes0);
239
+ import_node_assert.default.strictEqual(changes0.length, 1);
240
+ import_node_assert.default.strictEqual(changes0[0].newText, "sql.statement");
241
+ import_node_assert.default.ok(actions[1].title.includes("sql.fragment"));
242
+ import_node_assert.default.strictEqual(actions[1].kind, import_node.CodeActionKind.QuickFix);
243
+ const changes1 = actions[1].edit?.changes?.[uri];
244
+ import_node_assert.default.ok(changes1);
245
+ import_node_assert.default.strictEqual(changes1.length, 1);
246
+ import_node_assert.default.strictEqual(changes1[0].newText, "sql.fragment");
247
+ }
248
+ );
249
+ await t.test(
250
+ "returns empty array when no deprecated-sql-tag diagnostics",
251
+ () => {
252
+ const diagnostic = {
253
+ range: {
254
+ start: { line: 0, character: 0 },
255
+ end: { line: 0, character: 10 }
256
+ },
257
+ severity: 1,
258
+ message: "Invalid SQL: syntax error",
259
+ source: "moose-sql"
260
+ };
261
+ const actions = (0, import_codeActions.createDeprecationCodeActions)("file:///test.ts", [
262
+ diagnostic
263
+ ]);
264
+ import_node_assert.default.strictEqual(actions.length, 0);
265
+ }
266
+ );
267
+ });
206
268
  //# sourceMappingURL=codeActions.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codeActions.test.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { test } from 'node:test';\nimport ts from 'typescript';\nimport {\n createFormatSqlEdit,\n extractOriginalExpressions,\n findSqlTemplateAtPosition,\n findTemplateNodeById,\n} from './codeActions';\nimport type { SqlLocation } from './sqlLocations';\n\ntest('findSqlTemplateAtPosition Tests', async (t) => {\n const locations: SqlLocation[] = [\n {\n id: 'test.ts:5:10',\n file: '/project/test.ts',\n line: 5,\n column: 10,\n endLine: 5,\n endColumn: 50,\n templateText: 'SELECT * FROM users',\n },\n {\n id: 'test.ts:10:5',\n file: '/project/test.ts',\n line: 10,\n column: 5,\n endLine: 15,\n endColumn: 10,\n templateText: 'SELECT ${...} FROM ${...}',\n },\n ];\n\n await t.test('finds template when cursor is inside', () => {\n // Cursor at line 5, column 20 (inside first template)\n const result = findSqlTemplateAtPosition(locations, 4, 20); // 0-indexed\n\n assert.ok(result);\n assert.strictEqual(result.id, 'test.ts:5:10');\n });\n\n await t.test('finds multiline template when cursor is inside', () => {\n // Cursor at line 12 (inside second template which spans lines 10-15)\n const result = findSqlTemplateAtPosition(locations, 11, 5); // 0-indexed\n\n assert.ok(result);\n assert.strictEqual(result.id, 'test.ts:10:5');\n });\n\n await t.test('returns undefined when cursor is outside all templates', () => {\n // Cursor at line 1 (before any template)\n const result = findSqlTemplateAtPosition(locations, 0, 0);\n\n assert.strictEqual(result, undefined);\n });\n\n await t.test(\n 'returns undefined when cursor is after template on same line',\n () => {\n // Cursor at line 5, column 55 (after first template ends at 50)\n const result = findSqlTemplateAtPosition(locations, 4, 55);\n\n assert.strictEqual(result, undefined);\n },\n );\n\n await t.test('returns undefined for empty locations', () => {\n const result = findSqlTemplateAtPosition([], 5, 10);\n\n assert.strictEqual(result, undefined);\n });\n});\n\n// Helper to create a source file and find the first tagged template expression\nfunction createSourceFileWithSqlTemplate(code: string): {\n sourceFile: ts.SourceFile;\n templateNode: ts.TaggedTemplateExpression | undefined;\n} {\n const sourceFile = ts.createSourceFile(\n 'test.ts',\n code,\n ts.ScriptTarget.Latest,\n true,\n );\n\n let templateNode: ts.TaggedTemplateExpression | undefined;\n\n function visit(node: ts.Node): void {\n if (ts.isTaggedTemplateExpression(node)) {\n templateNode = node;\n }\n if (!templateNode) {\n ts.forEachChild(node, visit);\n }\n }\n\n visit(sourceFile);\n return { sourceFile, templateNode };\n}\n\ntest('extractOriginalExpressions Tests', async (t) => {\n await t.test('extracts expressions from template with substitutions', () => {\n const code = 'const q = sql`SELECT ${col} FROM ${table}`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 2);\n assert.strictEqual(expressions[0], '${col}');\n assert.strictEqual(expressions[1], '${table}');\n });\n\n await t.test('returns empty array for template without substitutions', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 0);\n });\n\n await t.test('handles complex expressions', () => {\n const code =\n 'const q = sql`SELECT * FROM ${getTable()} WHERE id = ${user.id}`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 2);\n assert.strictEqual(expressions[0], '${getTable()}');\n assert.strictEqual(expressions[1], '${user.id}');\n });\n});\n\ntest('findTemplateNodeById Tests', async (t) => {\n await t.test('finds template node by location id', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n\n // Get the expected location ID\n const start = sourceFile.getLineAndCharacterOfPosition(\n templateNode.template.getStart(),\n );\n const locationId = `test.ts:${start.line + 1}:${start.character + 1}`;\n\n const found = findTemplateNodeById(sourceFile, locationId);\n\n assert.ok(found);\n assert.strictEqual(found, templateNode);\n });\n\n await t.test('returns undefined for non-existent location id', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { sourceFile } = createSourceFileWithSqlTemplate(code);\n\n const found = findTemplateNodeById(sourceFile, 'test.ts:999:999');\n\n assert.strictEqual(found, undefined);\n });\n\n await t.test('ignores non-sql tagged templates', () => {\n const code = 'const q = html`<div>Hello</div>`;';\n const sourceFile = ts.createSourceFile(\n 'test.ts',\n code,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const found = findTemplateNodeById(sourceFile, 'test.ts:1:15');\n\n assert.strictEqual(found, undefined);\n });\n});\n\ntest('createFormatSqlEdit Tests', async (t) => {\n await t.test('creates edit for simple SQL template', () => {\n const code = 'const q = sql`select * from users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n assert.ok(edit.newText.includes('SELECT'));\n assert.ok(edit.newText.includes('FROM'));\n });\n\n await t.test('preserves placeholders in formatted output', () => {\n const code = 'const q = sql`select ${col} from ${table}`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n assert.ok(edit.newText.includes('${col}'));\n assert.ok(edit.newText.includes('${table}'));\n });\n\n await t.test('returns undefined for invalid SQL', () => {\n const code = 'const q = sql`SELCT * FROM users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.strictEqual(edit, undefined);\n });\n\n await t.test('preserves indentation for multiline template', () => {\n const code = `const q = sql\\`\n select * from users\n \\`;`;\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n // Should start with newline (preserving original structure)\n assert.ok(edit.newText.startsWith('\\n'), 'Should start with newline');\n // Should have indentation on content lines\n assert.ok(\n edit.newText.includes(' SELECT'),\n 'Should preserve 6-space indent',\n );\n });\n\n await t.test('preserves trailing newline and closing indent', () => {\n const code = `const q = sql\\`\n select * from users\n \\`;`;\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n // Should end with newline + trailing indent (4 spaces before backtick)\n assert.ok(\n edit.newText.endsWith('\\n '),\n 'Should end with newline and 4-space indent',\n );\n });\n});\n"],"mappings":"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","names":["assert","ts"]}
1
+ {"version":3,"sources":["../src/codeActions.test.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { test } from 'node:test';\nimport ts from 'typescript';\nimport { CodeActionKind } from 'vscode-languageserver/node';\nimport {\n createDeprecationCodeActions,\n createFormatSqlEdit,\n extractOriginalExpressions,\n findSqlTemplateAtPosition,\n findTemplateNodeById,\n} from './codeActions';\nimport type { SqlLocation } from './sqlLocations';\n\ntest('findSqlTemplateAtPosition Tests', async (t) => {\n const locations: SqlLocation[] = [\n {\n id: 'test.ts:5:10',\n file: '/project/test.ts',\n line: 5,\n column: 10,\n endLine: 5,\n endColumn: 50,\n templateText: 'SELECT * FROM users',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n },\n {\n id: 'test.ts:10:5',\n file: '/project/test.ts',\n line: 10,\n column: 5,\n endLine: 15,\n endColumn: 10,\n templateText: 'SELECT ${...} FROM ${...}',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n },\n ];\n\n await t.test('finds template when cursor is inside', () => {\n // Cursor at line 5, column 20 (inside first template)\n const result = findSqlTemplateAtPosition(locations, 4, 20); // 0-indexed\n\n assert.ok(result);\n assert.strictEqual(result.id, 'test.ts:5:10');\n });\n\n await t.test('finds multiline template when cursor is inside', () => {\n // Cursor at line 12 (inside second template which spans lines 10-15)\n const result = findSqlTemplateAtPosition(locations, 11, 5); // 0-indexed\n\n assert.ok(result);\n assert.strictEqual(result.id, 'test.ts:10:5');\n });\n\n await t.test('returns undefined when cursor is outside all templates', () => {\n // Cursor at line 1 (before any template)\n const result = findSqlTemplateAtPosition(locations, 0, 0);\n\n assert.strictEqual(result, undefined);\n });\n\n await t.test(\n 'returns undefined when cursor is after template on same line',\n () => {\n // Cursor at line 5, column 55 (after first template ends at 50)\n const result = findSqlTemplateAtPosition(locations, 4, 55);\n\n assert.strictEqual(result, undefined);\n },\n );\n\n await t.test('returns undefined for empty locations', () => {\n const result = findSqlTemplateAtPosition([], 5, 10);\n\n assert.strictEqual(result, undefined);\n });\n});\n\n// Helper to create a source file and find the first tagged template expression\nfunction createSourceFileWithSqlTemplate(code: string): {\n sourceFile: ts.SourceFile;\n templateNode: ts.TaggedTemplateExpression | undefined;\n} {\n const sourceFile = ts.createSourceFile(\n 'test.ts',\n code,\n ts.ScriptTarget.Latest,\n true,\n );\n\n let templateNode: ts.TaggedTemplateExpression | undefined;\n\n function visit(node: ts.Node): void {\n if (ts.isTaggedTemplateExpression(node)) {\n templateNode = node;\n }\n if (!templateNode) {\n ts.forEachChild(node, visit);\n }\n }\n\n visit(sourceFile);\n return { sourceFile, templateNode };\n}\n\ntest('extractOriginalExpressions Tests', async (t) => {\n await t.test('extracts expressions from template with substitutions', () => {\n const code = 'const q = sql`SELECT ${col} FROM ${table}`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 2);\n assert.strictEqual(expressions[0], '${col}');\n assert.strictEqual(expressions[1], '${table}');\n });\n\n await t.test('returns empty array for template without substitutions', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 0);\n });\n\n await t.test('handles complex expressions', () => {\n const code =\n 'const q = sql`SELECT * FROM ${getTable()} WHERE id = ${user.id}`;';\n const { templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const expressions = extractOriginalExpressions(templateNode.template);\n\n assert.strictEqual(expressions.length, 2);\n assert.strictEqual(expressions[0], '${getTable()}');\n assert.strictEqual(expressions[1], '${user.id}');\n });\n});\n\ntest('findTemplateNodeById Tests', async (t) => {\n await t.test('finds template node by location id', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n\n // Get the expected location ID\n const start = sourceFile.getLineAndCharacterOfPosition(\n templateNode.template.getStart(),\n );\n const locationId = `test.ts:${start.line + 1}:${start.character + 1}`;\n\n const found = findTemplateNodeById(sourceFile, locationId);\n\n assert.ok(found);\n assert.strictEqual(found, templateNode);\n });\n\n await t.test('returns undefined for non-existent location id', () => {\n const code = 'const q = sql`SELECT * FROM users`;';\n const { sourceFile } = createSourceFileWithSqlTemplate(code);\n\n const found = findTemplateNodeById(sourceFile, 'test.ts:999:999');\n\n assert.strictEqual(found, undefined);\n });\n\n await t.test('ignores non-sql tagged templates', () => {\n const code = 'const q = html`<div>Hello</div>`;';\n const sourceFile = ts.createSourceFile(\n 'test.ts',\n code,\n ts.ScriptTarget.Latest,\n true,\n );\n\n const found = findTemplateNodeById(sourceFile, 'test.ts:1:15');\n\n assert.strictEqual(found, undefined);\n });\n});\n\ntest('createFormatSqlEdit Tests', async (t) => {\n await t.test('creates edit for simple SQL template', () => {\n const code = 'const q = sql`select * from users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n assert.ok(edit.newText.includes('SELECT'));\n assert.ok(edit.newText.includes('FROM'));\n });\n\n await t.test('preserves placeholders in formatted output', () => {\n const code = 'const q = sql`select ${col} from ${table}`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n assert.ok(edit.newText.includes('${col}'));\n assert.ok(edit.newText.includes('${table}'));\n });\n\n await t.test('returns undefined for invalid SQL', () => {\n const code = 'const q = sql`SELCT * FROM users`;';\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.strictEqual(edit, undefined);\n });\n\n await t.test('preserves indentation for multiline template', () => {\n const code = `const q = sql\\`\n select * from users\n \\`;`;\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n // Should start with newline (preserving original structure)\n assert.ok(edit.newText.startsWith('\\n'), 'Should start with newline');\n // Should have indentation on content lines\n assert.ok(\n edit.newText.includes(' SELECT'),\n 'Should preserve 6-space indent',\n );\n });\n\n await t.test('preserves trailing newline and closing indent', () => {\n const code = `const q = sql\\`\n select * from users\n \\`;`;\n const { sourceFile, templateNode } = createSourceFileWithSqlTemplate(code);\n\n assert.ok(templateNode);\n const edit = createFormatSqlEdit(sourceFile, templateNode);\n\n assert.ok(edit);\n // Should end with newline + trailing indent (4 spaces before backtick)\n assert.ok(\n edit.newText.endsWith('\\n '),\n 'Should end with newline and 4-space indent',\n );\n });\n});\n\ntest('createDeprecationCodeActions Tests', async (t) => {\n await t.test(\n 'returns two code actions for deprecated-sql-tag diagnostic',\n () => {\n const diagnostic = {\n range: {\n start: { line: 0, character: 10 },\n end: { line: 0, character: 13 },\n },\n severity: 4 as const, // Hint\n tags: [2 as const], // Deprecated\n message: \"The 'sql' tag is deprecated...\",\n source: 'moose-sql',\n data: { type: 'deprecated-sql-tag' },\n };\n\n const uri = 'file:///project/test.ts';\n const actions = createDeprecationCodeActions(uri, [diagnostic]);\n\n assert.strictEqual(actions.length, 2);\n\n // First action: convert to sql.statement\n assert.ok(actions[0].title.includes('sql.statement'));\n assert.strictEqual(actions[0].kind, CodeActionKind.QuickFix);\n const changes0 = actions[0].edit?.changes?.[uri];\n assert.ok(changes0);\n assert.strictEqual(changes0.length, 1);\n assert.strictEqual(changes0[0].newText, 'sql.statement');\n\n // Second action: convert to sql.fragment\n assert.ok(actions[1].title.includes('sql.fragment'));\n assert.strictEqual(actions[1].kind, CodeActionKind.QuickFix);\n const changes1 = actions[1].edit?.changes?.[uri];\n assert.ok(changes1);\n assert.strictEqual(changes1.length, 1);\n assert.strictEqual(changes1[0].newText, 'sql.fragment');\n },\n );\n\n await t.test(\n 'returns empty array when no deprecated-sql-tag diagnostics',\n () => {\n const diagnostic = {\n range: {\n start: { line: 0, character: 0 },\n end: { line: 0, character: 10 },\n },\n severity: 1 as const,\n message: 'Invalid SQL: syntax error',\n source: 'moose-sql',\n };\n\n const actions = createDeprecationCodeActions('file:///test.ts', [\n diagnostic,\n ]);\n assert.strictEqual(actions.length, 0);\n },\n );\n});\n"],"mappings":"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","names":["assert","ts"]}
@@ -10,9 +10,17 @@ declare function createLocationDiagnostic(location: SqlLocation, validationError
10
10
  uri: string;
11
11
  diagnostic: Diagnostic;
12
12
  };
13
+ /**
14
+ * Creates an LSP deprecation hint for a bare `sql` tag.
15
+ * Uses Hint severity with the Deprecated diagnostic tag.
16
+ */
17
+ declare function createDeprecationDiagnostic(location: SqlLocation): {
18
+ uri: string;
19
+ diagnostic: Diagnostic;
20
+ };
13
21
  /**
14
22
  * Clears all diagnostics for the workspace
15
23
  */
16
24
  declare function clearDiagnostics(_connection: Connection, _projectRoot: string): void;
17
25
 
18
- export { clearDiagnostics, createLocationDiagnostic };
26
+ export { clearDiagnostics, createDeprecationDiagnostic, createLocationDiagnostic };
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var diagnostics_exports = {};
20
20
  __export(diagnostics_exports, {
21
21
  clearDiagnostics: () => clearDiagnostics,
22
+ createDeprecationDiagnostic: () => createDeprecationDiagnostic,
22
23
  createLocationDiagnostic: () => createLocationDiagnostic
23
24
  });
24
25
  module.exports = __toCommonJS(diagnostics_exports);
@@ -43,11 +44,34 @@ function createLocationDiagnostic(location, validationError) {
43
44
  };
44
45
  return { uri, diagnostic };
45
46
  }
47
+ function createDeprecationDiagnostic(location) {
48
+ const uri = `file://${location.file}`;
49
+ const range = {
50
+ start: {
51
+ line: location.tagLine - 1,
52
+ character: location.tagColumn - 1
53
+ },
54
+ end: {
55
+ line: location.tagLine - 1,
56
+ character: location.tagEndColumn - 1
57
+ }
58
+ };
59
+ const diagnostic = {
60
+ range,
61
+ severity: import_node.DiagnosticSeverity.Hint,
62
+ tags: [import_node.DiagnosticTag.Deprecated],
63
+ message: "The 'sql' tag is deprecated. Use 'sql.statement' for complete SQL statements or 'sql.fragment' for SQL expressions and conditions.",
64
+ source: "moose-sql",
65
+ data: { type: "deprecated-sql-tag" }
66
+ };
67
+ return { uri, diagnostic };
68
+ }
46
69
  function clearDiagnostics(_connection, _projectRoot) {
47
70
  }
48
71
  // Annotate the CommonJS export names for ESM import in node:
49
72
  0 && (module.exports = {
50
73
  clearDiagnostics,
74
+ createDeprecationDiagnostic,
51
75
  createLocationDiagnostic
52
76
  });
53
77
  //# sourceMappingURL=diagnostics.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["import type { ValidationResult } from '@514labs/moose-sql-validator-wasm';\nimport {\n type Connection,\n type Diagnostic,\n DiagnosticSeverity,\n type Range,\n} from 'vscode-languageserver/node';\nimport type { SqlLocation } from './sqlLocations';\n\n/**\n * Creates an LSP diagnostic from a SQL validation error for a SqlLocation.\n * This is used for inline sql template literals.\n */\nexport function createLocationDiagnostic(\n location: SqlLocation,\n validationError: NonNullable<ValidationResult['error']>,\n): { uri: string; diagnostic: Diagnostic } {\n const uri = `file://${location.file}`;\n\n // Convert 1-indexed source positions to 0-indexed LSP positions\n const range: Range = {\n start: {\n line: location.line - 1,\n character: location.column - 1,\n },\n end: {\n line: location.endLine - 1,\n character: location.endColumn - 1,\n },\n };\n\n const diagnostic: Diagnostic = {\n range,\n severity: DiagnosticSeverity.Error,\n message: `Invalid SQL: ${validationError.message}`,\n source: 'moose-sql',\n };\n\n return { uri, diagnostic };\n}\n\n/**\n * Clears all diagnostics for the workspace\n */\nexport function clearDiagnostics(\n _connection: Connection,\n _projectRoot: string,\n): void {\n // In a real implementation, we'd track which files have diagnostics\n // For now, this is a no-op as we'll republish on each save\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAKO;AAOA,SAAS,yBACd,UACA,iBACyC;AACzC,QAAM,MAAM,UAAU,SAAS,IAAI;AAGnC,QAAM,QAAe;AAAA,IACnB,OAAO;AAAA,MACL,MAAM,SAAS,OAAO;AAAA,MACtB,WAAW,SAAS,SAAS;AAAA,IAC/B;AAAA,IACA,KAAK;AAAA,MACH,MAAM,SAAS,UAAU;AAAA,MACzB,WAAW,SAAS,YAAY;AAAA,IAClC;AAAA,EACF;AAEA,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA,UAAU,+BAAmB;AAAA,IAC7B,SAAS,gBAAgB,gBAAgB,OAAO;AAAA,IAChD,QAAQ;AAAA,EACV;AAEA,SAAO,EAAE,KAAK,WAAW;AAC3B;AAKO,SAAS,iBACd,aACA,cACM;AAGR;","names":[]}
1
+ {"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["import type { ValidationResult } from '@514labs/moose-sql-validator-wasm';\nimport {\n type Connection,\n type Diagnostic,\n DiagnosticSeverity,\n DiagnosticTag,\n type Range,\n} from 'vscode-languageserver/node';\nimport type { SqlLocation } from './sqlLocations';\n\n/**\n * Creates an LSP diagnostic from a SQL validation error for a SqlLocation.\n * This is used for inline sql template literals.\n */\nexport function createLocationDiagnostic(\n location: SqlLocation,\n validationError: NonNullable<ValidationResult['error']>,\n): { uri: string; diagnostic: Diagnostic } {\n const uri = `file://${location.file}`;\n\n // Convert 1-indexed source positions to 0-indexed LSP positions\n const range: Range = {\n start: {\n line: location.line - 1,\n character: location.column - 1,\n },\n end: {\n line: location.endLine - 1,\n character: location.endColumn - 1,\n },\n };\n\n const diagnostic: Diagnostic = {\n range,\n severity: DiagnosticSeverity.Error,\n message: `Invalid SQL: ${validationError.message}`,\n source: 'moose-sql',\n };\n\n return { uri, diagnostic };\n}\n\n/**\n * Creates an LSP deprecation hint for a bare `sql` tag.\n * Uses Hint severity with the Deprecated diagnostic tag.\n */\nexport function createDeprecationDiagnostic(location: SqlLocation): {\n uri: string;\n diagnostic: Diagnostic;\n} {\n const uri = `file://${location.file}`;\n\n const range: Range = {\n start: {\n line: location.tagLine - 1,\n character: location.tagColumn - 1,\n },\n end: {\n line: location.tagLine - 1,\n character: location.tagEndColumn - 1,\n },\n };\n\n const diagnostic: Diagnostic = {\n range,\n severity: DiagnosticSeverity.Hint,\n tags: [DiagnosticTag.Deprecated],\n message:\n \"The 'sql' tag is deprecated. Use 'sql.statement' for complete SQL statements or 'sql.fragment' for SQL expressions and conditions.\",\n source: 'moose-sql',\n data: { type: 'deprecated-sql-tag' },\n };\n\n return { uri, diagnostic };\n}\n\n/**\n * Clears all diagnostics for the workspace\n */\nexport function clearDiagnostics(\n _connection: Connection,\n _projectRoot: string,\n): void {\n // In a real implementation, we'd track which files have diagnostics\n // For now, this is a no-op as we'll republish on each save\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,kBAMO;AAOA,SAAS,yBACd,UACA,iBACyC;AACzC,QAAM,MAAM,UAAU,SAAS,IAAI;AAGnC,QAAM,QAAe;AAAA,IACnB,OAAO;AAAA,MACL,MAAM,SAAS,OAAO;AAAA,MACtB,WAAW,SAAS,SAAS;AAAA,IAC/B;AAAA,IACA,KAAK;AAAA,MACH,MAAM,SAAS,UAAU;AAAA,MACzB,WAAW,SAAS,YAAY;AAAA,IAClC;AAAA,EACF;AAEA,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA,UAAU,+BAAmB;AAAA,IAC7B,SAAS,gBAAgB,gBAAgB,OAAO;AAAA,IAChD,QAAQ;AAAA,EACV;AAEA,SAAO,EAAE,KAAK,WAAW;AAC3B;AAMO,SAAS,4BAA4B,UAG1C;AACA,QAAM,MAAM,UAAU,SAAS,IAAI;AAEnC,QAAM,QAAe;AAAA,IACnB,OAAO;AAAA,MACL,MAAM,SAAS,UAAU;AAAA,MACzB,WAAW,SAAS,YAAY;AAAA,IAClC;AAAA,IACA,KAAK;AAAA,MACH,MAAM,SAAS,UAAU;AAAA,MACzB,WAAW,SAAS,eAAe;AAAA,IACrC;AAAA,EACF;AAEA,QAAM,aAAyB;AAAA,IAC7B;AAAA,IACA,UAAU,+BAAmB;AAAA,IAC7B,MAAM,CAAC,0BAAc,UAAU;AAAA,IAC/B,SACE;AAAA,IACF,QAAQ;AAAA,IACR,MAAM,EAAE,MAAM,qBAAqB;AAAA,EACrC;AAEA,SAAO,EAAE,KAAK,WAAW;AAC3B;AAKO,SAAS,iBACd,aACA,cACM;AAGR;","names":[]}
@@ -36,7 +36,11 @@ var import_diagnostics = require("./diagnostics");
36
36
  column: 22,
37
37
  endLine: 61,
38
38
  endColumn: 6,
39
- templateText: "SELECT ${...} FROM ${...}"
39
+ templateText: "SELECT ${...} FROM ${...}",
40
+ tagKind: "bare",
41
+ tagLine: 1,
42
+ tagColumn: 1,
43
+ tagEndColumn: 4
40
44
  };
41
45
  const validationError = { message: "Syntax error near SLECT" };
42
46
  const { uri } = (0, import_diagnostics.createLocationDiagnostic)(location, validationError);
@@ -53,7 +57,11 @@ var import_diagnostics = require("./diagnostics");
53
57
  // 1-indexed
54
58
  endLine: 61,
55
59
  endColumn: 6,
56
- templateText: "SELECT ${...}"
60
+ templateText: "SELECT ${...}",
61
+ tagKind: "bare",
62
+ tagLine: 1,
63
+ tagColumn: 1,
64
+ tagEndColumn: 4
57
65
  };
58
66
  const validationError = { message: "Error" };
59
67
  const { diagnostic } = (0, import_diagnostics.createLocationDiagnostic)(location, validationError);
@@ -70,7 +78,11 @@ var import_diagnostics = require("./diagnostics");
70
78
  column: 1,
71
79
  endLine: 1,
72
80
  endColumn: 50,
73
- templateText: "SLECT * FROM users"
81
+ templateText: "SLECT * FROM users",
82
+ tagKind: "bare",
83
+ tagLine: 1,
84
+ tagColumn: 1,
85
+ tagEndColumn: 4
74
86
  };
75
87
  const validationError = { message: "Expected SELECT, found SLECT" };
76
88
  const { diagnostic } = (0, import_diagnostics.createLocationDiagnostic)(location, validationError);
@@ -84,7 +96,11 @@ var import_diagnostics = require("./diagnostics");
84
96
  column: 1,
85
97
  endLine: 1,
86
98
  endColumn: 50,
87
- templateText: "SELECT"
99
+ templateText: "SELECT",
100
+ tagKind: "bare",
101
+ tagLine: 1,
102
+ tagColumn: 1,
103
+ tagEndColumn: 4
88
104
  };
89
105
  const validationError = { message: "Error" };
90
106
  const { diagnostic } = (0, import_diagnostics.createLocationDiagnostic)(location, validationError);
@@ -99,11 +115,46 @@ var import_diagnostics = require("./diagnostics");
99
115
  column: 22,
100
116
  endLine: 61,
101
117
  endColumn: 6,
102
- templateText: "SELECT"
118
+ templateText: "SELECT",
119
+ tagKind: "bare",
120
+ tagLine: 1,
121
+ tagColumn: 1,
122
+ tagEndColumn: 4
103
123
  };
104
124
  const validationError = { message: "Error" };
105
125
  const { diagnostic } = (0, import_diagnostics.createLocationDiagnostic)(location, validationError);
106
126
  import_node_assert.default.ok(diagnostic.message.includes("Invalid SQL"));
107
127
  });
108
128
  });
129
+ (0, import_node_test.test)("createDeprecationDiagnostic Tests", async (t) => {
130
+ await t.test(
131
+ "creates hint diagnostic with Deprecated tag for bare sql",
132
+ () => {
133
+ const location = {
134
+ id: "test.ts:1:15",
135
+ file: "/project/test.ts",
136
+ line: 1,
137
+ column: 15,
138
+ endLine: 1,
139
+ endColumn: 50,
140
+ templateText: "SELECT * FROM users",
141
+ tagKind: "bare",
142
+ tagLine: 1,
143
+ tagColumn: 11,
144
+ tagEndColumn: 14
145
+ };
146
+ const { uri, diagnostic } = (0, import_diagnostics.createDeprecationDiagnostic)(location);
147
+ import_node_assert.default.strictEqual(uri, "file:///project/test.ts");
148
+ import_node_assert.default.strictEqual(diagnostic.severity, import_node.DiagnosticSeverity.Hint);
149
+ import_node_assert.default.deepStrictEqual(diagnostic.tags, [import_node.DiagnosticTag.Deprecated]);
150
+ import_node_assert.default.strictEqual(diagnostic.source, "moose-sql");
151
+ import_node_assert.default.ok(diagnostic.message.includes("deprecated"));
152
+ import_node_assert.default.ok(diagnostic.message.includes("sql.statement"));
153
+ import_node_assert.default.ok(diagnostic.message.includes("sql.fragment"));
154
+ import_node_assert.default.strictEqual(diagnostic.range.start.line, 0);
155
+ import_node_assert.default.strictEqual(diagnostic.range.start.character, 10);
156
+ import_node_assert.default.strictEqual(diagnostic.range.end.character, 13);
157
+ }
158
+ );
159
+ });
109
160
  //# sourceMappingURL=diagnostics.test.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/diagnostics.test.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { test } from 'node:test';\nimport { DiagnosticSeverity } from 'vscode-languageserver/node';\nimport { createLocationDiagnostic } from './diagnostics';\nimport type { SqlLocation } from './sqlLocations';\n\ntest('createLocationDiagnostic Tests', async (t) => {\n await t.test(\n 'creates diagnostic with correct file URI from SqlLocation',\n () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54,\n column: 22,\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT ${...} FROM ${...}',\n };\n const validationError = { message: 'Syntax error near SLECT' };\n\n const { uri } = createLocationDiagnostic(location, validationError);\n\n assert.strictEqual(uri, 'file:///project/app/apis/bar.ts');\n },\n );\n\n await t.test('converts 1-indexed location to 0-indexed LSP range', () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54, // 1-indexed\n column: 22, // 1-indexed\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT ${...}',\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n // LSP uses 0-indexed positions\n assert.strictEqual(diagnostic.range.start.line, 53); // 54 - 1\n assert.strictEqual(diagnostic.range.start.character, 21); // 22 - 1\n assert.strictEqual(diagnostic.range.end.line, 60); // 61 - 1\n assert.strictEqual(diagnostic.range.end.character, 5); // 6 - 1\n });\n\n await t.test('includes error message in diagnostic', () => {\n const location: SqlLocation = {\n id: 'test.ts:1:1',\n file: '/project/test.ts',\n line: 1,\n column: 1,\n endLine: 1,\n endColumn: 50,\n templateText: 'SLECT * FROM users',\n };\n const validationError = { message: 'Expected SELECT, found SLECT' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n assert.ok(diagnostic.message.includes('Expected SELECT, found SLECT'));\n });\n\n await t.test('sets severity to Error and source to moose-sql', () => {\n const location: SqlLocation = {\n id: 'test.ts:1:1',\n file: '/project/test.ts',\n line: 1,\n column: 1,\n endLine: 1,\n endColumn: 50,\n templateText: 'SELECT',\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n assert.strictEqual(diagnostic.severity, DiagnosticSeverity.Error);\n assert.strictEqual(diagnostic.source, 'moose-sql');\n });\n\n await t.test('uses location id in diagnostic message', () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54,\n column: 22,\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT',\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n // The message should indicate it's from an inline SQL template\n assert.ok(diagnostic.message.includes('Invalid SQL'));\n });\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,yBAAmB;AACnB,uBAAqB;AACrB,kBAAmC;AACnC,yBAAyC;AAAA,IAGzC,uBAAK,kCAAkC,OAAO,MAAM;AAClD,QAAM,EAAE;AAAA,IACN;AAAA,IACA,MAAM;AACJ,YAAM,WAAwB;AAAA,QAC5B,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AACA,YAAM,kBAAkB,EAAE,SAAS,0BAA0B;AAE7D,YAAM,EAAE,IAAI,QAAI,6CAAyB,UAAU,eAAe;AAElE,yBAAAA,QAAO,YAAY,KAAK,iCAAiC;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,EAAE,KAAK,sDAAsD,MAAM;AACvE,UAAM,WAAwB;AAAA,MAC5B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA;AAAA,MACN,QAAQ;AAAA;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,MACX,cAAc;AAAA,IAChB;AACA,UAAM,kBAAkB,EAAE,SAAS,QAAQ;AAE3C,UAAM,EAAE,WAAW,QAAI,6CAAyB,UAAU,eAAe;AAGzE,uBAAAA,QAAO,YAAY,WAAW,MAAM,MAAM,MAAM,EAAE;AAClD,uBAAAA,QAAO,YAAY,WAAW,MAAM,MAAM,WAAW,EAAE;AACvD,uBAAAA,QAAO,YAAY,WAAW,MAAM,IAAI,MAAM,EAAE;AAChD,uBAAAA,QAAO,YAAY,WAAW,MAAM,IAAI,WAAW,CAAC;AAAA,EACtD,CAAC;AAED,QAAM,EAAE,KAAK,wCAAwC,MAAM;AACzD,UAAM,WAAwB;AAAA,MAC5B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,MACX,cAAc;AAAA,IAChB;AACA,UAAM,kBAAkB,EAAE,SAAS,+BAA+B;AAElE,UAAM,EAAE,WAAW,QAAI,6CAAyB,UAAU,eAAe;AAEzE,uBAAAA,QAAO,GAAG,WAAW,QAAQ,SAAS,8BAA8B,CAAC;AAAA,EACvE,CAAC;AAED,QAAM,EAAE,KAAK,kDAAkD,MAAM;AACnE,UAAM,WAAwB;AAAA,MAC5B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,MACX,cAAc;AAAA,IAChB;AACA,UAAM,kBAAkB,EAAE,SAAS,QAAQ;AAE3C,UAAM,EAAE,WAAW,QAAI,6CAAyB,UAAU,eAAe;AAEzE,uBAAAA,QAAO,YAAY,WAAW,UAAU,+BAAmB,KAAK;AAChE,uBAAAA,QAAO,YAAY,WAAW,QAAQ,WAAW;AAAA,EACnD,CAAC;AAED,QAAM,EAAE,KAAK,0CAA0C,MAAM;AAC3D,UAAM,WAAwB;AAAA,MAC5B,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,WAAW;AAAA,MACX,cAAc;AAAA,IAChB;AACA,UAAM,kBAAkB,EAAE,SAAS,QAAQ;AAE3C,UAAM,EAAE,WAAW,QAAI,6CAAyB,UAAU,eAAe;AAGzE,uBAAAA,QAAO,GAAG,WAAW,QAAQ,SAAS,aAAa,CAAC;AAAA,EACtD,CAAC;AACH,CAAC;","names":["assert"]}
1
+ {"version":3,"sources":["../src/diagnostics.test.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { test } from 'node:test';\nimport { DiagnosticSeverity, DiagnosticTag } from 'vscode-languageserver/node';\nimport {\n createDeprecationDiagnostic,\n createLocationDiagnostic,\n} from './diagnostics';\nimport type { SqlLocation } from './sqlLocations';\n\ntest('createLocationDiagnostic Tests', async (t) => {\n await t.test(\n 'creates diagnostic with correct file URI from SqlLocation',\n () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54,\n column: 22,\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT ${...} FROM ${...}',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n };\n const validationError = { message: 'Syntax error near SLECT' };\n\n const { uri } = createLocationDiagnostic(location, validationError);\n\n assert.strictEqual(uri, 'file:///project/app/apis/bar.ts');\n },\n );\n\n await t.test('converts 1-indexed location to 0-indexed LSP range', () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54, // 1-indexed\n column: 22, // 1-indexed\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT ${...}',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n // LSP uses 0-indexed positions\n assert.strictEqual(diagnostic.range.start.line, 53); // 54 - 1\n assert.strictEqual(diagnostic.range.start.character, 21); // 22 - 1\n assert.strictEqual(diagnostic.range.end.line, 60); // 61 - 1\n assert.strictEqual(diagnostic.range.end.character, 5); // 6 - 1\n });\n\n await t.test('includes error message in diagnostic', () => {\n const location: SqlLocation = {\n id: 'test.ts:1:1',\n file: '/project/test.ts',\n line: 1,\n column: 1,\n endLine: 1,\n endColumn: 50,\n templateText: 'SLECT * FROM users',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n };\n const validationError = { message: 'Expected SELECT, found SLECT' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n assert.ok(diagnostic.message.includes('Expected SELECT, found SLECT'));\n });\n\n await t.test('sets severity to Error and source to moose-sql', () => {\n const location: SqlLocation = {\n id: 'test.ts:1:1',\n file: '/project/test.ts',\n line: 1,\n column: 1,\n endLine: 1,\n endColumn: 50,\n templateText: 'SELECT',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n assert.strictEqual(diagnostic.severity, DiagnosticSeverity.Error);\n assert.strictEqual(diagnostic.source, 'moose-sql');\n });\n\n await t.test('uses location id in diagnostic message', () => {\n const location: SqlLocation = {\n id: 'app/apis/bar.ts:54:22',\n file: '/project/app/apis/bar.ts',\n line: 54,\n column: 22,\n endLine: 61,\n endColumn: 6,\n templateText: 'SELECT',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 1,\n tagEndColumn: 4,\n };\n const validationError = { message: 'Error' };\n\n const { diagnostic } = createLocationDiagnostic(location, validationError);\n\n // The message should indicate it's from an inline SQL template\n assert.ok(diagnostic.message.includes('Invalid SQL'));\n });\n});\n\ntest('createDeprecationDiagnostic Tests', async (t) => {\n await t.test(\n 'creates hint diagnostic with Deprecated tag for bare sql',\n () => {\n const location: SqlLocation = {\n id: 'test.ts:1:15',\n file: '/project/test.ts',\n line: 1,\n column: 15,\n endLine: 1,\n endColumn: 50,\n templateText: 'SELECT * FROM users',\n tagKind: 'bare',\n tagLine: 1,\n tagColumn: 11,\n tagEndColumn: 14,\n };\n\n const { uri, diagnostic } = createDeprecationDiagnostic(location);\n\n assert.strictEqual(uri, 'file:///project/test.ts');\n assert.strictEqual(diagnostic.severity, DiagnosticSeverity.Hint);\n assert.deepStrictEqual(diagnostic.tags, [DiagnosticTag.Deprecated]);\n assert.strictEqual(diagnostic.source, 'moose-sql');\n assert.ok(diagnostic.message.includes('deprecated'));\n assert.ok(diagnostic.message.includes('sql.statement'));\n assert.ok(diagnostic.message.includes('sql.fragment'));\n // Range should cover just the tag, not the template\n assert.strictEqual(diagnostic.range.start.line, 0); // 1-indexed -> 0-indexed\n assert.strictEqual(diagnostic.range.start.character, 10); // 11 -> 10\n assert.strictEqual(diagnostic.range.end.character, 13); // 14 -> 13\n },\n );\n});\n"],"mappings":"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","names":["assert"]}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@514labs/moose-sql-validator-wasm",
3
- "version": "1.3.12000161",
3
+ "version": "1.4.1",
4
4
  "description": "WASM SQL validator for ClickHouse dialect",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -215,7 +215,11 @@ function createSqlLocation(filePath, text, startPosition, endPosition) {
215
215
  // 1-based
216
216
  endLine: endPosition.row + 1,
217
217
  endColumn: endPosition.column + 1,
218
- templateText: text
218
+ templateText: text,
219
+ tagKind: "statement",
220
+ tagLine: startPosition.row + 1,
221
+ tagColumn: startPosition.column + 1,
222
+ tagEndColumn: startPosition.column + 1
219
223
  };
220
224
  }
221
225
  function extractPythonSqlLocations(sourceCode, filePath) {