@514labs/moose-lsp 1.4.0 → 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.
- package/dist/codeActions.d.ts +7 -2
- package/dist/codeActions.js +57 -10
- package/dist/codeActions.js.map +1 -1
- package/dist/codeActions.test.js +64 -2
- package/dist/codeActions.test.js.map +1 -1
- package/dist/diagnostics.d.ts +9 -1
- package/dist/diagnostics.js +24 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/diagnostics.test.js +56 -5
- package/dist/diagnostics.test.js.map +1 -1
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/package.json +1 -1
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/pkg/sql_validator_bg.wasm +0 -0
- package/dist/pythonSqlExtractor.js +5 -1
- package/dist/pythonSqlExtractor.js.map +1 -1
- package/dist/server.integration.test.js +222 -26
- package/dist/server.integration.test.js.map +1 -1
- package/dist/server.js +209 -87
- package/dist/server.js.map +1 -1
- package/dist/serverLogic.js +9 -0
- package/dist/serverLogic.js.map +1 -1
- package/dist/serverLogic.test.js +127 -10
- package/dist/serverLogic.test.js.map +1 -1
- package/dist/sqlExtractor.js +43 -12
- package/dist/sqlExtractor.js.map +1 -1
- package/dist/sqlExtractor.test.js +106 -4
- package/dist/sqlExtractor.test.js.map +1 -1
- package/dist/sqlLocations.d.ts +9 -1
- package/dist/sqlLocations.js +9 -1
- package/dist/sqlLocations.js.map +1 -1
- package/dist/sqlLocations.test.js +49 -1
- package/dist/sqlLocations.test.js.map +1 -1
- package/package.json +1 -1
package/dist/codeActions.d.ts
CHANGED
|
@@ -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 };
|
package/dist/codeActions.js
CHANGED
|
@@ -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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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,
|
package/dist/codeActions.js.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAe;AACf,kBAKO;AACP,wBAAkC;AAU3B,SAAS,0BACd,WACA,MACA,WACyB;AAEzB,QAAM,aAAa,OAAO;AAC1B,QAAM,eAAe,YAAY;AAEjC,aAAW,OAAO,WAAW;AAE3B,UAAM,aACJ,aAAa,IAAI,QAChB,eAAe,IAAI,QAAQ,gBAAgB,IAAI;AAElD,UAAM,YACJ,aAAa,IAAI,WAChB,eAAe,IAAI,WAAW,gBAAgB,IAAI;AAErD,QAAI,cAAc,WAAW;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAMO,SAAS,2BACd,UACU;AACV,MAAI,kBAAAA,QAAG,gCAAgC,QAAQ,GAAG;AAChD,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,cAAwB,CAAC;AAC/B,aAAW,QAAQ,SAAS,eAAe;AACzC,UAAM,WAAW,KAAK,WAAW,QAAQ;AACzC,gBAAY,KAAK,MAAM,QAAQ,GAAG;AAAA,EACpC;AACA,SAAO;AACT;AAKA,SAAS,oBAAoB,UAAsC;AACjE,MAAI,kBAAAA,QAAG,gCAAgC,QAAQ,GAAG;AAChD,WAAO,SAAS;AAAA,EAClB;AAEA,MAAI,OAAO,SAAS,KAAK;AACzB,aAAW,QAAQ,SAAS,eAAe;AACzC,YAAQ,UAAU,KAAK,QAAQ,IAAI;AAAA,EACrC;AACA,SAAO;AACT;AAMA,SAAS,0BAA0B,cAKjC;AACA,QAAM,oBAAoB,aAAa,WAAW,IAAI;AACtD,QAAM,qBACJ,aAAa,SAAS,IAAI,KAAK,SAAS,KAAK,YAAY;AAG3D,MAAI,SAAS;AACb,MAAI,mBAAmB;AACrB,UAAM,QAAQ,aAAa,MAAM,IAAI;AAErC,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,OAAO,MAAM,CAAC;AACpB,UAAI,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1B,cAAM,QAAQ,KAAK,MAAM,QAAQ;AACjC,YAAI,OAAO;AACT,mBAAS,MAAM,CAAC;AAAA,QAClB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,iBAAiB;AACrB,MAAI,oBAAoB;AACtB,UAAM,QAAQ,aAAa,MAAM,UAAU;AAC3C,QAAI,OAAO;AACT,uBAAiB,MAAM,CAAC;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO,EAAE,QAAQ,mBAAmB,oBAAoB,eAAe;AACzE;AAKA,SAAS,iBACP,WACA,QACA,mBACA,oBACA,gBACQ;AAER,QAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,QAAM,gBAAgB,MAAM;AAAA,IAAI,CAAC,SAC/B,KAAK,SAAS,IAAI,SAAS,OAAO;AAAA,EACpC;AAEA,MAAI,SAAS,cAAc,KAAK,IAAI;AAGpC,MAAI,mBAAmB;AACrB,aAAS;AAAA,EAAK,MAAM;AAAA,EACtB;AAGA,MAAI,oBAAoB;AACtB,aAAS,GAAG,MAAM;AAAA,EAAK,cAAc;AAAA,EACvC;AAEA,SAAO;AACT;AAKO,SAAS,oBACd,YACA,MACsB;AACtB,QAAM,gBAAgB,2BAA2B,KAAK,QAAQ;AAC9D,QAAM,eAAe,oBAAoB,KAAK,QAAQ;AAEtD,QAAM,aAAS,qCAAkB,cAAc,aAAa;AAC5D,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,WAAW;AACxC,WAAO;AAAA,EACT;AAGA,QAAM,EAAE,QAAQ,mBAAmB,oBAAoB,eAAe,IACpE,0BAA0B,YAAY;AAGxC,QAAM,sBAAsB;AAAA,IAC1B,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,gBAAgB,WAAW;AAAA,IAC/B,KAAK,SAAS,SAAS,IAAI;AAAA,EAC7B;AACA,QAAM,cAAc,WAAW;AAAA,IAC7B,KAAK,SAAS,OAAO,IAAI;AAAA,EAC3B;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,MACL,OAAO,EAAE,MAAM,cAAc,MAAM,WAAW,cAAc,UAAU;AAAA,MACtE,KAAK,EAAE,MAAM,YAAY,MAAM,WAAW,YAAY,UAAU;AAAA,IAClE;AAAA,IACA,SAAS;AAAA,EACX;AACF;AAKO,SAAS,qBACd,YACA,YACyC;AACzC,MAAI;AAEJ,WAAS,SAAS,KAA6B;AAC7C,QAAI,kBAAAA,QAAG,aAAa,GAAG,KAAK,IAAI,SAAS,MAAO,QAAO;AACvD,QACE,kBAAAA,QAAG,2BAA2B,GAAG,KACjC,kBAAAA,QAAG,aAAa,IAAI,UAAU,KAC9B,IAAI,WAAW,SAAS,UACvB,IAAI,KAAK,SAAS,eAAe,IAAI,KAAK,SAAS,aACpD;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,WAAS,MAAM,MAAqB;AAClC,QAAI,kBAAAA,QAAG,2BAA2B,IAAI,KAAK,SAAS,KAAK,GAAG,GAAG;AAC7D,YAAM,QAAQ,WAAW;AAAA,QACvB,KAAK,SAAS,SAAS;AAAA,MACzB;AACA,YAAM,KAAK,GAAG,WAAW,QAAQ,IAAI,MAAM,OAAO,CAAC,IAAI,MAAM,YAAY,CAAC;AAC1E,UAAI,OAAO,YAAY;AACrB,qBAAa;AAAA,MACf;AAAA,IACF;AACA,QAAI,CAAC,YAAY;AACf,wBAAAA,QAAG,aAAa,MAAM,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,QAAM,UAAU;AAChB,SAAO;AACT;AAMO,SAAS,6BACd,KACA,aACc;AACd,QAAM,UAAwB,CAAC;AAE/B,aAAW,cAAc,aAAa;AACpC,UAAM,OAAO,WAAW;AACxB,QAAI,MAAM,SAAS,qBAAsB;AAEzC,YAAQ;AAAA,MACN;AAAA,QACE,OAAO;AAAA,QACP,MAAM,2BAAe;AAAA,QACrB,aAAa,CAAC,UAAU;AAAA,QACxB,MAAM;AAAA,UACJ,SAAS;AAAA,YACP,CAAC,GAAG,GAAG;AAAA,cACL;AAAA,gBACE,OAAO,WAAW;AAAA,gBAClB,SAAS;AAAA,cACX;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,MAAM,2BAAe;AAAA,QACrB,aAAa,CAAC,UAAU;AAAA,QACxB,MAAM;AAAA,UACJ,SAAS;AAAA,YACP,CAAC,GAAG,GAAG;AAAA,cACL;AAAA,gBACE,OAAO,WAAW;AAAA,gBAClB,SAAS;AAAA,cACX;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":["ts"]}
|
package/dist/codeActions.test.js
CHANGED
|
@@ -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"]}
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -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 };
|
package/dist/diagnostics.js
CHANGED
|
@@ -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
|
package/dist/diagnostics.js.map
CHANGED
|
@@ -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,
|
|
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":[]}
|
package/dist/diagnostics.test.js
CHANGED
|
@@ -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 {
|
|
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"]}
|
|
Binary file
|
|
@@ -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) {
|