@514labs/moose-lsp 0.1.0-dev.11.3.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/clickhouseData.d.ts +78 -0
- package/dist/clickhouseData.js +111 -0
- package/dist/clickhouseData.js.map +1 -0
- package/dist/clickhouseData.test.d.ts +2 -0
- package/dist/clickhouseData.test.js +92 -0
- package/dist/clickhouseData.test.js.map +1 -0
- package/dist/clickhouseVersion.d.ts +49 -0
- package/dist/clickhouseVersion.js +148 -0
- package/dist/clickhouseVersion.js.map +1 -0
- package/dist/clickhouseVersion.test.d.ts +2 -0
- package/dist/clickhouseVersion.test.js +113 -0
- package/dist/clickhouseVersion.test.js.map +1 -0
- package/dist/codeActions.d.ts +27 -0
- package/dist/codeActions.js +172 -0
- package/dist/codeActions.js.map +1 -0
- package/dist/codeActions.test.d.ts +2 -0
- package/dist/codeActions.test.js +206 -0
- package/dist/codeActions.test.js.map +1 -0
- package/dist/completions.d.ts +22 -0
- package/dist/completions.js +227 -0
- package/dist/completions.js.map +1 -0
- package/dist/completions.test.d.ts +2 -0
- package/dist/completions.test.js +282 -0
- package/dist/completions.test.js.map +1 -0
- package/dist/data/clickhouse-25.6.json +30772 -0
- package/dist/data/clickhouse-25.8.json +31872 -0
- package/dist/diagnostics.d.ts +18 -0
- package/dist/diagnostics.js +53 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/diagnostics.test.d.ts +2 -0
- package/dist/diagnostics.test.js +109 -0
- package/dist/diagnostics.test.js.map +1 -0
- package/dist/formatting.d.ts +36 -0
- package/dist/formatting.js +69 -0
- package/dist/formatting.js.map +1 -0
- package/dist/formatting.test.d.ts +2 -0
- package/dist/formatting.test.js +92 -0
- package/dist/formatting.test.js.map +1 -0
- package/dist/hover.d.ts +44 -0
- package/dist/hover.js +209 -0
- package/dist/hover.js.map +1 -0
- package/dist/hover.test.d.ts +2 -0
- package/dist/hover.test.js +354 -0
- package/dist/hover.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/dist/index.js +56 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/dist/index.js.map +1 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/dist/index.test.js +185 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/dist/index.test.js.map +1 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/package.json +40 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/pkg/package.json +11 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/pkg/sql_validator.js +176 -0
- package/dist/node_modules/@514labs/moose-sql-validator-wasm/pkg/sql_validator_bg.wasm +0 -0
- package/dist/projectDetector.d.ts +21 -0
- package/dist/projectDetector.js +162 -0
- package/dist/projectDetector.js.map +1 -0
- package/dist/projectDetector.test.d.ts +2 -0
- package/dist/projectDetector.test.js +303 -0
- package/dist/projectDetector.test.js.map +1 -0
- package/dist/pythonService.d.ts +40 -0
- package/dist/pythonService.js +121 -0
- package/dist/pythonService.js.map +1 -0
- package/dist/pythonService.test.d.ts +2 -0
- package/dist/pythonService.test.js +208 -0
- package/dist/pythonService.test.js.map +1 -0
- package/dist/pythonSqlExtractor.d.ts +25 -0
- package/dist/pythonSqlExtractor.js +258 -0
- package/dist/pythonSqlExtractor.js.map +1 -0
- package/dist/pythonSqlExtractor.test.d.ts +2 -0
- package/dist/pythonSqlExtractor.test.js +227 -0
- package/dist/pythonSqlExtractor.test.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.integration.test.d.ts +2 -0
- package/dist/server.integration.test.js +189 -0
- package/dist/server.integration.test.js.map +1 -0
- package/dist/server.js +228412 -0
- package/dist/server.js.map +1 -0
- package/dist/serverLogic.d.ts +40 -0
- package/dist/serverLogic.js +61 -0
- package/dist/serverLogic.js.map +1 -0
- package/dist/serverLogic.test.d.ts +2 -0
- package/dist/serverLogic.test.js +263 -0
- package/dist/serverLogic.test.js.map +1 -0
- package/dist/sqlExtractor.d.ts +15 -0
- package/dist/sqlExtractor.js +105 -0
- package/dist/sqlExtractor.js.map +1 -0
- package/dist/sqlExtractor.test.d.ts +2 -0
- package/dist/sqlExtractor.test.js +267 -0
- package/dist/sqlExtractor.test.js.map +1 -0
- package/dist/sqlLocations.d.ts +31 -0
- package/dist/sqlLocations.js +51 -0
- package/dist/sqlLocations.js.map +1 -0
- package/dist/sqlLocations.test.d.ts +2 -0
- package/dist/sqlLocations.test.js +94 -0
- package/dist/sqlLocations.test.js.map +1 -0
- package/dist/typescriptService.d.ts +29 -0
- package/dist/typescriptService.js +137 -0
- package/dist/typescriptService.js.map +1 -0
- package/dist/typescriptService.test.d.ts +2 -0
- package/dist/typescriptService.test.js +200 -0
- package/dist/typescriptService.test.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ValidationResult } from '@514labs/moose-sql-validator-wasm';
|
|
2
|
+
import { Diagnostic } from 'vscode-languageserver/node';
|
|
3
|
+
import { SqlLocation } from './sqlLocations.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Function type for SQL validation
|
|
7
|
+
*/
|
|
8
|
+
type ValidateSqlFn = (sql: string) => ValidationResult;
|
|
9
|
+
/**
|
|
10
|
+
* Function type for creating diagnostics from SqlLocation
|
|
11
|
+
*/
|
|
12
|
+
type CreateLocationDiagnosticFn = (location: SqlLocation, error: NonNullable<ValidationResult['error']>) => {
|
|
13
|
+
uri: string;
|
|
14
|
+
diagnostic: Diagnostic;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Determines if a file should be validated based on path and project root
|
|
18
|
+
* @param filePath - The absolute path to the file
|
|
19
|
+
* @param mooseProjectRoot - The root of the Moose project (or null if not detected)
|
|
20
|
+
* @returns true if the file should be validated
|
|
21
|
+
*/
|
|
22
|
+
declare function shouldValidateFile(filePath: string, mooseProjectRoot: string | null): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Determines if a file is a TypeScript file
|
|
25
|
+
*/
|
|
26
|
+
declare function isTypeScriptFile(filePath: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Determines if a file is a Python file
|
|
29
|
+
*/
|
|
30
|
+
declare function isPythonFile(filePath: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Validates SQL from template locations and returns a map of URI -> diagnostics
|
|
33
|
+
* @param sqlLocations - Array of SQL template locations
|
|
34
|
+
* @param validateSql - Function to validate SQL strings
|
|
35
|
+
* @param createDiagnostic - Function to create LSP diagnostics from validation errors
|
|
36
|
+
* @returns Map of file URIs to their diagnostics
|
|
37
|
+
*/
|
|
38
|
+
declare function validateSqlLocations(sqlLocations: SqlLocation[], validateSql: ValidateSqlFn, createDiagnostic: CreateLocationDiagnosticFn): Map<string, Diagnostic[]>;
|
|
39
|
+
|
|
40
|
+
export { type CreateLocationDiagnosticFn, type ValidateSqlFn, isPythonFile, isTypeScriptFile, shouldValidateFile, validateSqlLocations };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var serverLogic_exports = {};
|
|
20
|
+
__export(serverLogic_exports, {
|
|
21
|
+
isPythonFile: () => isPythonFile,
|
|
22
|
+
isTypeScriptFile: () => isTypeScriptFile,
|
|
23
|
+
shouldValidateFile: () => shouldValidateFile,
|
|
24
|
+
validateSqlLocations: () => validateSqlLocations
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(serverLogic_exports);
|
|
27
|
+
var import_sqlLocations = require("./sqlLocations");
|
|
28
|
+
function shouldValidateFile(filePath, mooseProjectRoot) {
|
|
29
|
+
if (!mooseProjectRoot) return false;
|
|
30
|
+
if (!filePath.startsWith(mooseProjectRoot)) return false;
|
|
31
|
+
return filePath.endsWith(".ts") || filePath.endsWith(".py");
|
|
32
|
+
}
|
|
33
|
+
function isTypeScriptFile(filePath) {
|
|
34
|
+
return filePath.endsWith(".ts") || filePath.endsWith(".tsx");
|
|
35
|
+
}
|
|
36
|
+
function isPythonFile(filePath) {
|
|
37
|
+
return filePath.endsWith(".py");
|
|
38
|
+
}
|
|
39
|
+
function validateSqlLocations(sqlLocations, validateSql, createDiagnostic) {
|
|
40
|
+
const diagnosticsMap = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const location of sqlLocations) {
|
|
42
|
+
const preparedSql = (0, import_sqlLocations.prepareSqlForValidation)(location.templateText);
|
|
43
|
+
const result = validateSql(preparedSql);
|
|
44
|
+
if (!result.valid && result.error) {
|
|
45
|
+
const { uri, diagnostic } = createDiagnostic(location, result.error);
|
|
46
|
+
if (!diagnosticsMap.has(uri)) {
|
|
47
|
+
diagnosticsMap.set(uri, []);
|
|
48
|
+
}
|
|
49
|
+
diagnosticsMap.get(uri)?.push(diagnostic);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return diagnosticsMap;
|
|
53
|
+
}
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
isPythonFile,
|
|
57
|
+
isTypeScriptFile,
|
|
58
|
+
shouldValidateFile,
|
|
59
|
+
validateSqlLocations
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=serverLogic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serverLogic.ts"],"sourcesContent":["import type { ValidationResult } from '@514labs/moose-sql-validator-wasm';\nimport type { Diagnostic } from 'vscode-languageserver/node';\nimport { prepareSqlForValidation, type SqlLocation } from './sqlLocations';\n\n/**\n * Function type for SQL validation\n */\nexport type ValidateSqlFn = (sql: string) => ValidationResult;\n\n/**\n * Function type for creating diagnostics from SqlLocation\n */\nexport type CreateLocationDiagnosticFn = (\n location: SqlLocation,\n error: NonNullable<ValidationResult['error']>,\n) => { uri: string; diagnostic: Diagnostic };\n\n/**\n * Determines if a file should be validated based on path and project root\n * @param filePath - The absolute path to the file\n * @param mooseProjectRoot - The root of the Moose project (or null if not detected)\n * @returns true if the file should be validated\n */\nexport function shouldValidateFile(\n filePath: string,\n mooseProjectRoot: string | null,\n): boolean {\n if (!mooseProjectRoot) return false;\n if (!filePath.startsWith(mooseProjectRoot)) return false;\n return filePath.endsWith('.ts') || filePath.endsWith('.py');\n}\n\n/**\n * Determines if a file is a TypeScript file\n */\nexport function isTypeScriptFile(filePath: string): boolean {\n return filePath.endsWith('.ts') || filePath.endsWith('.tsx');\n}\n\n/**\n * Determines if a file is a Python file\n */\nexport function isPythonFile(filePath: string): boolean {\n return filePath.endsWith('.py');\n}\n\n/**\n * Validates SQL from template locations and returns a map of URI -> diagnostics\n * @param sqlLocations - Array of SQL template locations\n * @param validateSql - Function to validate SQL strings\n * @param createDiagnostic - Function to create LSP diagnostics from validation errors\n * @returns Map of file URIs to their diagnostics\n */\nexport function validateSqlLocations(\n sqlLocations: SqlLocation[],\n validateSql: ValidateSqlFn,\n createDiagnostic: CreateLocationDiagnosticFn,\n): Map<string, Diagnostic[]> {\n const diagnosticsMap = new Map<string, Diagnostic[]>();\n\n for (const location of sqlLocations) {\n // Replace ${...} placeholders with valid SQL identifiers before validation\n const preparedSql = prepareSqlForValidation(location.templateText);\n const result = validateSql(preparedSql);\n\n if (!result.valid && result.error) {\n const { uri, diagnostic } = createDiagnostic(location, result.error);\n\n if (!diagnosticsMap.has(uri)) {\n diagnosticsMap.set(uri, []);\n }\n diagnosticsMap.get(uri)?.push(diagnostic);\n }\n }\n\n return diagnosticsMap;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,0BAA0D;AAqBnD,SAAS,mBACd,UACA,kBACS;AACT,MAAI,CAAC,iBAAkB,QAAO;AAC9B,MAAI,CAAC,SAAS,WAAW,gBAAgB,EAAG,QAAO;AACnD,SAAO,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,KAAK;AAC5D;AAKO,SAAS,iBAAiB,UAA2B;AAC1D,SAAO,SAAS,SAAS,KAAK,KAAK,SAAS,SAAS,MAAM;AAC7D;AAKO,SAAS,aAAa,UAA2B;AACtD,SAAO,SAAS,SAAS,KAAK;AAChC;AASO,SAAS,qBACd,cACA,aACA,kBAC2B;AAC3B,QAAM,iBAAiB,oBAAI,IAA0B;AAErD,aAAW,YAAY,cAAc;AAEnC,UAAM,kBAAc,6CAAwB,SAAS,YAAY;AACjE,UAAM,SAAS,YAAY,WAAW;AAEtC,QAAI,CAAC,OAAO,SAAS,OAAO,OAAO;AACjC,YAAM,EAAE,KAAK,WAAW,IAAI,iBAAiB,UAAU,OAAO,KAAK;AAEnE,UAAI,CAAC,eAAe,IAAI,GAAG,GAAG;AAC5B,uBAAe,IAAI,KAAK,CAAC,CAAC;AAAA,MAC5B;AACA,qBAAe,IAAI,GAAG,GAAG,KAAK,UAAU;AAAA,IAC1C;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var import_node_assert = __toESM(require("node:assert"));
|
|
25
|
+
var import_node_test = require("node:test");
|
|
26
|
+
var import_serverLogic = require("./serverLogic");
|
|
27
|
+
function createMockDiagnostic(message) {
|
|
28
|
+
const range = {
|
|
29
|
+
start: { line: 0, character: 0 },
|
|
30
|
+
end: { line: 0, character: 10 }
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
range,
|
|
34
|
+
message,
|
|
35
|
+
severity: 1,
|
|
36
|
+
// Error
|
|
37
|
+
source: "moose-sql"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
(0, import_node_test.test)("shouldValidateFile Tests", async (t) => {
|
|
41
|
+
await t.test("returns false when mooseProjectRoot is null", () => {
|
|
42
|
+
import_node_assert.default.strictEqual((0, import_serverLogic.shouldValidateFile)("/some/path/file.ts", null), false);
|
|
43
|
+
});
|
|
44
|
+
await t.test("returns false for non-TypeScript files", () => {
|
|
45
|
+
const projectRoot = "/home/user/project";
|
|
46
|
+
import_node_assert.default.strictEqual(
|
|
47
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project/src/index.js", projectRoot),
|
|
48
|
+
false
|
|
49
|
+
);
|
|
50
|
+
import_node_assert.default.strictEqual(
|
|
51
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project/package.json", projectRoot),
|
|
52
|
+
false
|
|
53
|
+
);
|
|
54
|
+
import_node_assert.default.strictEqual(
|
|
55
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project/README.md", projectRoot),
|
|
56
|
+
false
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
await t.test("returns false for files outside project", () => {
|
|
60
|
+
const projectRoot = "/home/user/project";
|
|
61
|
+
import_node_assert.default.strictEqual(
|
|
62
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/other-project/src/index.ts", projectRoot),
|
|
63
|
+
false
|
|
64
|
+
);
|
|
65
|
+
import_node_assert.default.strictEqual((0, import_serverLogic.shouldValidateFile)("/tmp/file.ts", projectRoot), false);
|
|
66
|
+
});
|
|
67
|
+
await t.test("returns true for .ts files in project", () => {
|
|
68
|
+
const projectRoot = "/home/user/project";
|
|
69
|
+
import_node_assert.default.strictEqual(
|
|
70
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project/src/index.ts", projectRoot),
|
|
71
|
+
true
|
|
72
|
+
);
|
|
73
|
+
import_node_assert.default.strictEqual(
|
|
74
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project/app/models/user.ts", projectRoot),
|
|
75
|
+
true
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
await t.test("handles edge case where file path equals project root", () => {
|
|
79
|
+
const projectRoot = "/home/user/project";
|
|
80
|
+
import_node_assert.default.strictEqual(
|
|
81
|
+
(0, import_serverLogic.shouldValidateFile)("/home/user/project", projectRoot),
|
|
82
|
+
false
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
(0, import_node_test.test)("validateSqlLocations Tests", async (t) => {
|
|
87
|
+
await t.test("returns empty map for empty locations", () => {
|
|
88
|
+
const mockValidateSql = () => ({ valid: true });
|
|
89
|
+
const mockCreateDiagnostic = () => ({
|
|
90
|
+
uri: "",
|
|
91
|
+
diagnostic: createMockDiagnostic("")
|
|
92
|
+
});
|
|
93
|
+
const result = (0, import_serverLogic.validateSqlLocations)(
|
|
94
|
+
[],
|
|
95
|
+
mockValidateSql,
|
|
96
|
+
mockCreateDiagnostic
|
|
97
|
+
);
|
|
98
|
+
import_node_assert.default.strictEqual(result.size, 0);
|
|
99
|
+
});
|
|
100
|
+
await t.test("returns diagnostics for invalid SQL in template", () => {
|
|
101
|
+
const locations = [
|
|
102
|
+
{
|
|
103
|
+
id: "app/apis/bar.ts:54:22",
|
|
104
|
+
file: "/project/app/apis/bar.ts",
|
|
105
|
+
line: 54,
|
|
106
|
+
column: 22,
|
|
107
|
+
endLine: 61,
|
|
108
|
+
endColumn: 6,
|
|
109
|
+
templateText: "SLECT ${...} FROM ${...}"
|
|
110
|
+
// typo
|
|
111
|
+
}
|
|
112
|
+
];
|
|
113
|
+
const mockValidateSql = () => ({
|
|
114
|
+
valid: false,
|
|
115
|
+
error: { message: "Expected SELECT, found SLECT" }
|
|
116
|
+
});
|
|
117
|
+
const mockCreateDiagnostic = (location, error) => ({
|
|
118
|
+
uri: `file://${location.file}`,
|
|
119
|
+
diagnostic: createMockDiagnostic(error.message)
|
|
120
|
+
});
|
|
121
|
+
const result = (0, import_serverLogic.validateSqlLocations)(
|
|
122
|
+
locations,
|
|
123
|
+
mockValidateSql,
|
|
124
|
+
mockCreateDiagnostic
|
|
125
|
+
);
|
|
126
|
+
import_node_assert.default.strictEqual(result.size, 1);
|
|
127
|
+
const diagnostics = result.get("file:///project/app/apis/bar.ts");
|
|
128
|
+
import_node_assert.default.ok(diagnostics);
|
|
129
|
+
import_node_assert.default.strictEqual(diagnostics.length, 1);
|
|
130
|
+
import_node_assert.default.strictEqual(diagnostics[0].message, "Expected SELECT, found SLECT");
|
|
131
|
+
});
|
|
132
|
+
await t.test("skips valid SQL", () => {
|
|
133
|
+
const locations = [
|
|
134
|
+
{
|
|
135
|
+
id: "app/apis/bar.ts:54:22",
|
|
136
|
+
file: "/project/app/apis/bar.ts",
|
|
137
|
+
line: 54,
|
|
138
|
+
column: 22,
|
|
139
|
+
endLine: 61,
|
|
140
|
+
endColumn: 6,
|
|
141
|
+
templateText: "SELECT ${...} FROM ${...}"
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
const mockValidateSql = () => ({ valid: true });
|
|
145
|
+
const mockCreateDiagnostic = () => ({
|
|
146
|
+
uri: "file:///project/app/apis/bar.ts",
|
|
147
|
+
diagnostic: createMockDiagnostic("Should not be called")
|
|
148
|
+
});
|
|
149
|
+
const result = (0, import_serverLogic.validateSqlLocations)(
|
|
150
|
+
locations,
|
|
151
|
+
mockValidateSql,
|
|
152
|
+
mockCreateDiagnostic
|
|
153
|
+
);
|
|
154
|
+
import_node_assert.default.strictEqual(result.size, 0);
|
|
155
|
+
});
|
|
156
|
+
await t.test("groups diagnostics by file URI", () => {
|
|
157
|
+
const locations = [
|
|
158
|
+
{
|
|
159
|
+
id: "app/apis/bar.ts:54:22",
|
|
160
|
+
file: "/project/app/apis/bar.ts",
|
|
161
|
+
line: 54,
|
|
162
|
+
column: 22,
|
|
163
|
+
endLine: 61,
|
|
164
|
+
endColumn: 6,
|
|
165
|
+
templateText: "SLECT ${...}"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "app/apis/bar.ts:100:22",
|
|
169
|
+
file: "/project/app/apis/bar.ts",
|
|
170
|
+
line: 100,
|
|
171
|
+
column: 22,
|
|
172
|
+
endLine: 105,
|
|
173
|
+
endColumn: 6,
|
|
174
|
+
templateText: "SELCT ${...}"
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
const mockValidateSql = () => ({
|
|
178
|
+
valid: false,
|
|
179
|
+
error: { message: "Syntax error" }
|
|
180
|
+
});
|
|
181
|
+
const mockCreateDiagnostic = (location, error) => ({
|
|
182
|
+
uri: `file://${location.file}`,
|
|
183
|
+
diagnostic: createMockDiagnostic(error.message)
|
|
184
|
+
});
|
|
185
|
+
const result = (0, import_serverLogic.validateSqlLocations)(
|
|
186
|
+
locations,
|
|
187
|
+
mockValidateSql,
|
|
188
|
+
mockCreateDiagnostic
|
|
189
|
+
);
|
|
190
|
+
import_node_assert.default.strictEqual(result.size, 1);
|
|
191
|
+
const diagnostics = result.get("file:///project/app/apis/bar.ts");
|
|
192
|
+
import_node_assert.default.ok(diagnostics);
|
|
193
|
+
import_node_assert.default.strictEqual(diagnostics.length, 2);
|
|
194
|
+
});
|
|
195
|
+
await t.test("handles multiple files", () => {
|
|
196
|
+
const locations = [
|
|
197
|
+
{
|
|
198
|
+
id: "app/apis/foo.ts:10:5",
|
|
199
|
+
file: "/project/app/apis/foo.ts",
|
|
200
|
+
line: 10,
|
|
201
|
+
column: 5,
|
|
202
|
+
endLine: 15,
|
|
203
|
+
endColumn: 6,
|
|
204
|
+
templateText: "SLECT ${...}"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "app/apis/bar.ts:54:22",
|
|
208
|
+
file: "/project/app/apis/bar.ts",
|
|
209
|
+
line: 54,
|
|
210
|
+
column: 22,
|
|
211
|
+
endLine: 61,
|
|
212
|
+
endColumn: 6,
|
|
213
|
+
templateText: "SELCT ${...}"
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
const mockValidateSql = () => ({
|
|
217
|
+
valid: false,
|
|
218
|
+
error: { message: "Syntax error" }
|
|
219
|
+
});
|
|
220
|
+
const mockCreateDiagnostic = (location, error) => ({
|
|
221
|
+
uri: `file://${location.file}`,
|
|
222
|
+
diagnostic: createMockDiagnostic(error.message)
|
|
223
|
+
});
|
|
224
|
+
const result = (0, import_serverLogic.validateSqlLocations)(
|
|
225
|
+
locations,
|
|
226
|
+
mockValidateSql,
|
|
227
|
+
mockCreateDiagnostic
|
|
228
|
+
);
|
|
229
|
+
import_node_assert.default.strictEqual(result.size, 2);
|
|
230
|
+
import_node_assert.default.ok(result.has("file:///project/app/apis/foo.ts"));
|
|
231
|
+
import_node_assert.default.ok(result.has("file:///project/app/apis/bar.ts"));
|
|
232
|
+
});
|
|
233
|
+
await t.test(
|
|
234
|
+
"prepares SQL by replacing ${...} placeholders before validation",
|
|
235
|
+
() => {
|
|
236
|
+
const locations = [
|
|
237
|
+
{
|
|
238
|
+
id: "test.ts:1:1",
|
|
239
|
+
file: "/project/test.ts",
|
|
240
|
+
line: 1,
|
|
241
|
+
column: 1,
|
|
242
|
+
endLine: 1,
|
|
243
|
+
endColumn: 50,
|
|
244
|
+
templateText: "SELECT ${...} FROM ${...}"
|
|
245
|
+
}
|
|
246
|
+
];
|
|
247
|
+
let validatedSql = "";
|
|
248
|
+
const mockValidateSql = (sql) => {
|
|
249
|
+
validatedSql = sql;
|
|
250
|
+
return { valid: true };
|
|
251
|
+
};
|
|
252
|
+
const mockCreateDiagnostic = () => ({
|
|
253
|
+
uri: "",
|
|
254
|
+
diagnostic: createMockDiagnostic("")
|
|
255
|
+
});
|
|
256
|
+
(0, import_serverLogic.validateSqlLocations)(locations, mockValidateSql, mockCreateDiagnostic);
|
|
257
|
+
import_node_assert.default.ok(!validatedSql.includes("${...}"));
|
|
258
|
+
import_node_assert.default.ok(validatedSql.includes("SELECT"));
|
|
259
|
+
import_node_assert.default.ok(validatedSql.includes("FROM"));
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
//# sourceMappingURL=serverLogic.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serverLogic.test.ts"],"sourcesContent":["import assert from 'node:assert';\nimport { test } from 'node:test';\nimport type { Diagnostic, Range } from 'vscode-languageserver/node';\nimport {\n type CreateLocationDiagnosticFn,\n shouldValidateFile,\n type ValidateSqlFn,\n validateSqlLocations,\n} from './serverLogic';\nimport type { SqlLocation } from './sqlLocations';\n\n// Helper to create a mock diagnostic\nfunction createMockDiagnostic(message: string): Diagnostic {\n const range: Range = {\n start: { line: 0, character: 0 },\n end: { line: 0, character: 10 },\n };\n return {\n range,\n message,\n severity: 1, // Error\n source: 'moose-sql',\n };\n}\n\ntest('shouldValidateFile Tests', async (t) => {\n await t.test('returns false when mooseProjectRoot is null', () => {\n assert.strictEqual(shouldValidateFile('/some/path/file.ts', null), false);\n });\n\n await t.test('returns false for non-TypeScript files', () => {\n const projectRoot = '/home/user/project';\n\n assert.strictEqual(\n shouldValidateFile('/home/user/project/src/index.js', projectRoot),\n false,\n );\n assert.strictEqual(\n shouldValidateFile('/home/user/project/package.json', projectRoot),\n false,\n );\n assert.strictEqual(\n shouldValidateFile('/home/user/project/README.md', projectRoot),\n false,\n );\n });\n\n await t.test('returns false for files outside project', () => {\n const projectRoot = '/home/user/project';\n\n assert.strictEqual(\n shouldValidateFile('/home/user/other-project/src/index.ts', projectRoot),\n false,\n );\n assert.strictEqual(shouldValidateFile('/tmp/file.ts', projectRoot), false);\n });\n\n await t.test('returns true for .ts files in project', () => {\n const projectRoot = '/home/user/project';\n\n assert.strictEqual(\n shouldValidateFile('/home/user/project/src/index.ts', projectRoot),\n true,\n );\n assert.strictEqual(\n shouldValidateFile('/home/user/project/app/models/user.ts', projectRoot),\n true,\n );\n });\n\n await t.test('handles edge case where file path equals project root', () => {\n const projectRoot = '/home/user/project';\n\n // A file can't be the same as the project root and end in .ts\n assert.strictEqual(\n shouldValidateFile('/home/user/project', projectRoot),\n false,\n );\n });\n});\n\ntest('validateSqlLocations Tests', async (t) => {\n await t.test('returns empty map for empty locations', () => {\n const mockValidateSql: ValidateSqlFn = () => ({ valid: true });\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = () => ({\n uri: '',\n diagnostic: createMockDiagnostic(''),\n });\n\n const result = validateSqlLocations(\n [],\n mockValidateSql,\n mockCreateDiagnostic,\n );\n\n assert.strictEqual(result.size, 0);\n });\n\n await t.test('returns diagnostics for invalid SQL in template', () => {\n const locations: SqlLocation[] = [\n {\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: 'SLECT ${...} FROM ${...}', // typo\n },\n ];\n\n const mockValidateSql: ValidateSqlFn = () => ({\n valid: false,\n error: { message: 'Expected SELECT, found SLECT' },\n });\n\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = (\n location,\n error,\n ) => ({\n uri: `file://${location.file}`,\n diagnostic: createMockDiagnostic(error.message),\n });\n\n const result = validateSqlLocations(\n locations,\n mockValidateSql,\n mockCreateDiagnostic,\n );\n\n assert.strictEqual(result.size, 1);\n const diagnostics = result.get('file:///project/app/apis/bar.ts');\n assert.ok(diagnostics);\n assert.strictEqual(diagnostics.length, 1);\n assert.strictEqual(diagnostics[0].message, 'Expected SELECT, found SLECT');\n });\n\n await t.test('skips valid SQL', () => {\n const locations: SqlLocation[] = [\n {\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 ];\n\n const mockValidateSql: ValidateSqlFn = () => ({ valid: true });\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = () => ({\n uri: 'file:///project/app/apis/bar.ts',\n diagnostic: createMockDiagnostic('Should not be called'),\n });\n\n const result = validateSqlLocations(\n locations,\n mockValidateSql,\n mockCreateDiagnostic,\n );\n\n assert.strictEqual(result.size, 0);\n });\n\n await t.test('groups diagnostics by file URI', () => {\n const locations: SqlLocation[] = [\n {\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: 'SLECT ${...}',\n },\n {\n id: 'app/apis/bar.ts:100:22',\n file: '/project/app/apis/bar.ts',\n line: 100,\n column: 22,\n endLine: 105,\n endColumn: 6,\n templateText: 'SELCT ${...}',\n },\n ];\n\n const mockValidateSql: ValidateSqlFn = () => ({\n valid: false,\n error: { message: 'Syntax error' },\n });\n\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = (\n location,\n error,\n ) => ({\n uri: `file://${location.file}`,\n diagnostic: createMockDiagnostic(error.message),\n });\n\n const result = validateSqlLocations(\n locations,\n mockValidateSql,\n mockCreateDiagnostic,\n );\n\n assert.strictEqual(result.size, 1);\n const diagnostics = result.get('file:///project/app/apis/bar.ts');\n assert.ok(diagnostics);\n assert.strictEqual(diagnostics.length, 2);\n });\n\n await t.test('handles multiple files', () => {\n const locations: SqlLocation[] = [\n {\n id: 'app/apis/foo.ts:10:5',\n file: '/project/app/apis/foo.ts',\n line: 10,\n column: 5,\n endLine: 15,\n endColumn: 6,\n templateText: 'SLECT ${...}',\n },\n {\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: 'SELCT ${...}',\n },\n ];\n\n const mockValidateSql: ValidateSqlFn = () => ({\n valid: false,\n error: { message: 'Syntax error' },\n });\n\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = (\n location,\n error,\n ) => ({\n uri: `file://${location.file}`,\n diagnostic: createMockDiagnostic(error.message),\n });\n\n const result = validateSqlLocations(\n locations,\n mockValidateSql,\n mockCreateDiagnostic,\n );\n\n assert.strictEqual(result.size, 2);\n assert.ok(result.has('file:///project/app/apis/foo.ts'));\n assert.ok(result.has('file:///project/app/apis/bar.ts'));\n });\n\n await t.test(\n 'prepares SQL by replacing ${...} placeholders before validation',\n () => {\n const locations: SqlLocation[] = [\n {\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 ${...} FROM ${...}',\n },\n ];\n\n let validatedSql = '';\n const mockValidateSql: ValidateSqlFn = (sql) => {\n validatedSql = sql;\n return { valid: true };\n };\n\n const mockCreateDiagnostic: CreateLocationDiagnosticFn = () => ({\n uri: '',\n diagnostic: createMockDiagnostic(''),\n });\n\n validateSqlLocations(locations, mockValidateSql, mockCreateDiagnostic);\n\n // Should have replaced ${...} with placeholders\n assert.ok(!validatedSql.includes('${...}'));\n assert.ok(validatedSql.includes('SELECT'));\n assert.ok(validatedSql.includes('FROM'));\n },\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAAA,yBAAmB;AACnB,uBAAqB;AAErB,yBAKO;AAIP,SAAS,qBAAqB,SAA6B;AACzD,QAAM,QAAe;AAAA,IACnB,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE;AAAA,IAC/B,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG;AAAA,EAChC;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU;AAAA;AAAA,IACV,QAAQ;AAAA,EACV;AACF;AAAA,IAEA,uBAAK,4BAA4B,OAAO,MAAM;AAC5C,QAAM,EAAE,KAAK,+CAA+C,MAAM;AAChE,uBAAAA,QAAO,gBAAY,uCAAmB,sBAAsB,IAAI,GAAG,KAAK;AAAA,EAC1E,CAAC;AAED,QAAM,EAAE,KAAK,0CAA0C,MAAM;AAC3D,UAAM,cAAc;AAEpB,uBAAAA,QAAO;AAAA,UACL,uCAAmB,mCAAmC,WAAW;AAAA,MACjE;AAAA,IACF;AACA,uBAAAA,QAAO;AAAA,UACL,uCAAmB,mCAAmC,WAAW;AAAA,MACjE;AAAA,IACF;AACA,uBAAAA,QAAO;AAAA,UACL,uCAAmB,gCAAgC,WAAW;AAAA,MAC9D;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,EAAE,KAAK,2CAA2C,MAAM;AAC5D,UAAM,cAAc;AAEpB,uBAAAA,QAAO;AAAA,UACL,uCAAmB,yCAAyC,WAAW;AAAA,MACvE;AAAA,IACF;AACA,uBAAAA,QAAO,gBAAY,uCAAmB,gBAAgB,WAAW,GAAG,KAAK;AAAA,EAC3E,CAAC;AAED,QAAM,EAAE,KAAK,yCAAyC,MAAM;AAC1D,UAAM,cAAc;AAEpB,uBAAAA,QAAO;AAAA,UACL,uCAAmB,mCAAmC,WAAW;AAAA,MACjE;AAAA,IACF;AACA,uBAAAA,QAAO;AAAA,UACL,uCAAmB,yCAAyC,WAAW;AAAA,MACvE;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,EAAE,KAAK,yDAAyD,MAAM;AAC1E,UAAM,cAAc;AAGpB,uBAAAA,QAAO;AAAA,UACL,uCAAmB,sBAAsB,WAAW;AAAA,MACpD;AAAA,IACF;AAAA,EACF,CAAC;AACH,CAAC;AAAA,IAED,uBAAK,8BAA8B,OAAO,MAAM;AAC9C,QAAM,EAAE,KAAK,yCAAyC,MAAM;AAC1D,UAAM,kBAAiC,OAAO,EAAE,OAAO,KAAK;AAC5D,UAAM,uBAAmD,OAAO;AAAA,MAC9D,KAAK;AAAA,MACL,YAAY,qBAAqB,EAAE;AAAA,IACrC;AAEA,UAAM,aAAS;AAAA,MACb,CAAC;AAAA,MACD;AAAA,MACA;AAAA,IACF;AAEA,uBAAAA,QAAO,YAAY,OAAO,MAAM,CAAC;AAAA,EACnC,CAAC;AAED,QAAM,EAAE,KAAK,mDAAmD,MAAM;AACpE,UAAM,YAA2B;AAAA,MAC/B;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,kBAAiC,OAAO;AAAA,MAC5C,OAAO;AAAA,MACP,OAAO,EAAE,SAAS,+BAA+B;AAAA,IACnD;AAEA,UAAM,uBAAmD,CACvD,UACA,WACI;AAAA,MACJ,KAAK,UAAU,SAAS,IAAI;AAAA,MAC5B,YAAY,qBAAqB,MAAM,OAAO;AAAA,IAChD;AAEA,UAAM,aAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,uBAAAA,QAAO,YAAY,OAAO,MAAM,CAAC;AACjC,UAAM,cAAc,OAAO,IAAI,iCAAiC;AAChE,uBAAAA,QAAO,GAAG,WAAW;AACrB,uBAAAA,QAAO,YAAY,YAAY,QAAQ,CAAC;AACxC,uBAAAA,QAAO,YAAY,YAAY,CAAC,EAAE,SAAS,8BAA8B;AAAA,EAC3E,CAAC;AAED,QAAM,EAAE,KAAK,mBAAmB,MAAM;AACpC,UAAM,YAA2B;AAAA,MAC/B;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,kBAAiC,OAAO,EAAE,OAAO,KAAK;AAC5D,UAAM,uBAAmD,OAAO;AAAA,MAC9D,KAAK;AAAA,MACL,YAAY,qBAAqB,sBAAsB;AAAA,IACzD;AAEA,UAAM,aAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,uBAAAA,QAAO,YAAY,OAAO,MAAM,CAAC;AAAA,EACnC,CAAC;AAED,QAAM,EAAE,KAAK,kCAAkC,MAAM;AACnD,UAAM,YAA2B;AAAA,MAC/B;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,kBAAiC,OAAO;AAAA,MAC5C,OAAO;AAAA,MACP,OAAO,EAAE,SAAS,eAAe;AAAA,IACnC;AAEA,UAAM,uBAAmD,CACvD,UACA,WACI;AAAA,MACJ,KAAK,UAAU,SAAS,IAAI;AAAA,MAC5B,YAAY,qBAAqB,MAAM,OAAO;AAAA,IAChD;AAEA,UAAM,aAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,uBAAAA,QAAO,YAAY,OAAO,MAAM,CAAC;AACjC,UAAM,cAAc,OAAO,IAAI,iCAAiC;AAChE,uBAAAA,QAAO,GAAG,WAAW;AACrB,uBAAAA,QAAO,YAAY,YAAY,QAAQ,CAAC;AAAA,EAC1C,CAAC;AAED,QAAM,EAAE,KAAK,0BAA0B,MAAM;AAC3C,UAAM,YAA2B;AAAA,MAC/B;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,WAAW;AAAA,QACX,cAAc;AAAA,MAChB;AAAA,IACF;AAEA,UAAM,kBAAiC,OAAO;AAAA,MAC5C,OAAO;AAAA,MACP,OAAO,EAAE,SAAS,eAAe;AAAA,IACnC;AAEA,UAAM,uBAAmD,CACvD,UACA,WACI;AAAA,MACJ,KAAK,UAAU,SAAS,IAAI;AAAA,MAC5B,YAAY,qBAAqB,MAAM,OAAO;AAAA,IAChD;AAEA,UAAM,aAAS;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,uBAAAA,QAAO,YAAY,OAAO,MAAM,CAAC;AACjC,uBAAAA,QAAO,GAAG,OAAO,IAAI,iCAAiC,CAAC;AACvD,uBAAAA,QAAO,GAAG,OAAO,IAAI,iCAAiC,CAAC;AAAA,EACzD,CAAC;AAED,QAAM,EAAE;AAAA,IACN;AAAA,IACA,MAAM;AACJ,YAAM,YAA2B;AAAA,QAC/B;AAAA,UACE,IAAI;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,WAAW;AAAA,UACX,cAAc;AAAA,QAChB;AAAA,MACF;AAEA,UAAI,eAAe;AACnB,YAAM,kBAAiC,CAAC,QAAQ;AAC9C,uBAAe;AACf,eAAO,EAAE,OAAO,KAAK;AAAA,MACvB;AAEA,YAAM,uBAAmD,OAAO;AAAA,QAC9D,KAAK;AAAA,QACL,YAAY,qBAAqB,EAAE;AAAA,MACrC;AAEA,mDAAqB,WAAW,iBAAiB,oBAAoB;AAGrE,yBAAAA,QAAO,GAAG,CAAC,aAAa,SAAS,QAAQ,CAAC;AAC1C,yBAAAA,QAAO,GAAG,aAAa,SAAS,QAAQ,CAAC;AACzC,yBAAAA,QAAO,GAAG,aAAa,SAAS,MAAM,CAAC;AAAA,IACzC;AAAA,EACF;AACF,CAAC;","names":["assert"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { SqlLocation } from './sqlLocations.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract all SQL locations from a single source file.
|
|
6
|
+
* Uses the TypeChecker to verify that the `sql` tag comes from moose-lib.
|
|
7
|
+
*/
|
|
8
|
+
declare function extractSqlLocations(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): SqlLocation[];
|
|
9
|
+
/**
|
|
10
|
+
* Extract SQL locations from all source files (for initial scan).
|
|
11
|
+
* Iterates through all source files in the program and collects SQL templates.
|
|
12
|
+
*/
|
|
13
|
+
declare function extractAllSqlLocations(sourceFiles: readonly ts.SourceFile[], typeChecker: ts.TypeChecker): SqlLocation[];
|
|
14
|
+
|
|
15
|
+
export { extractAllSqlLocations, extractSqlLocations };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var sqlExtractor_exports = {};
|
|
30
|
+
__export(sqlExtractor_exports, {
|
|
31
|
+
extractAllSqlLocations: () => extractAllSqlLocations,
|
|
32
|
+
extractSqlLocations: () => extractSqlLocations
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(sqlExtractor_exports);
|
|
35
|
+
var import_typescript = __toESM(require("typescript"));
|
|
36
|
+
function isMooseLibSqlTag(node, typeChecker) {
|
|
37
|
+
const tag = node.tag;
|
|
38
|
+
if (!import_typescript.default.isIdentifier(tag) || tag.text !== "sql") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const symbol = typeChecker.getSymbolAtLocation(tag);
|
|
42
|
+
if (symbol?.declarations?.length) {
|
|
43
|
+
const isFromMooseLib = symbol.declarations.some((decl) => {
|
|
44
|
+
const sourceFile = decl.getSourceFile();
|
|
45
|
+
const fileName = sourceFile.fileName;
|
|
46
|
+
return fileName.includes("moose-lib") || fileName.includes("@514labs/moose-lib") || fileName.includes("514labs/moose-lib") || fileName.includes("sqlHelpers");
|
|
47
|
+
});
|
|
48
|
+
if (isFromMooseLib) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function extractTemplateText(template) {
|
|
55
|
+
if (import_typescript.default.isNoSubstitutionTemplateLiteral(template)) {
|
|
56
|
+
return template.text;
|
|
57
|
+
}
|
|
58
|
+
let text = template.head.text;
|
|
59
|
+
for (const span of template.templateSpans) {
|
|
60
|
+
text += `\${...}${span.literal.text}`;
|
|
61
|
+
}
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
function extractSqlLocation(node, sourceFile) {
|
|
65
|
+
const start = sourceFile.getLineAndCharacterOfPosition(
|
|
66
|
+
node.template.getStart()
|
|
67
|
+
);
|
|
68
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.template.getEnd());
|
|
69
|
+
return {
|
|
70
|
+
id: `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`,
|
|
71
|
+
file: sourceFile.fileName,
|
|
72
|
+
line: start.line + 1,
|
|
73
|
+
// 1-based
|
|
74
|
+
column: start.character + 1,
|
|
75
|
+
// 1-based
|
|
76
|
+
endLine: end.line + 1,
|
|
77
|
+
endColumn: end.character + 1,
|
|
78
|
+
templateText: extractTemplateText(node.template)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function extractSqlLocations(sourceFile, typeChecker) {
|
|
82
|
+
const locations = [];
|
|
83
|
+
function visit(node) {
|
|
84
|
+
if (import_typescript.default.isTaggedTemplateExpression(node) && isMooseLibSqlTag(node, typeChecker)) {
|
|
85
|
+
locations.push(extractSqlLocation(node, sourceFile));
|
|
86
|
+
}
|
|
87
|
+
import_typescript.default.forEachChild(node, visit);
|
|
88
|
+
}
|
|
89
|
+
visit(sourceFile);
|
|
90
|
+
return locations;
|
|
91
|
+
}
|
|
92
|
+
function extractAllSqlLocations(sourceFiles, typeChecker) {
|
|
93
|
+
const allLocations = [];
|
|
94
|
+
for (const sourceFile of sourceFiles) {
|
|
95
|
+
const locations = extractSqlLocations(sourceFile, typeChecker);
|
|
96
|
+
allLocations.push(...locations);
|
|
97
|
+
}
|
|
98
|
+
return allLocations;
|
|
99
|
+
}
|
|
100
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
101
|
+
0 && (module.exports = {
|
|
102
|
+
extractAllSqlLocations,
|
|
103
|
+
extractSqlLocations
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=sqlExtractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sqlExtractor.ts"],"sourcesContent":["import ts from 'typescript';\nimport type { SqlLocation } from './sqlLocations';\n\n/**\n * Check if the sql tag comes from @514labs/moose-lib.\n * Falls back to returning true if symbol can't be resolved\n * (better to have false positives than miss real sql queries).\n */\nfunction isMooseLibSqlTag(\n node: ts.TaggedTemplateExpression,\n typeChecker: ts.TypeChecker,\n): boolean {\n const tag = node.tag;\n\n // Must be a simple identifier `sql`\n if (!ts.isIdentifier(tag) || tag.text !== 'sql') {\n return false;\n }\n\n const symbol = typeChecker.getSymbolAtLocation(tag);\n if (symbol?.declarations?.length) {\n // Check if any declaration originates from moose-lib\n const isFromMooseLib = symbol.declarations.some((decl) => {\n const sourceFile = decl.getSourceFile();\n const fileName = sourceFile.fileName;\n return (\n fileName.includes('moose-lib') ||\n fileName.includes('@514labs/moose-lib') ||\n fileName.includes('514labs/moose-lib') ||\n fileName.includes('sqlHelpers')\n );\n });\n if (isFromMooseLib) {\n return true;\n }\n }\n\n // Fallback: if we can't resolve the symbol, assume it's our sql tag\n // (better to have false positives than miss real sql queries)\n return true;\n}\n\n/**\n * Extract template text with ${...} placeholders.\n * Converts template literals like `SELECT ${col} FROM ${table}`\n * into \"SELECT ${...} FROM ${...}\" for SQL validation.\n */\nfunction extractTemplateText(template: ts.TemplateLiteral): string {\n if (ts.isNoSubstitutionTemplateLiteral(template)) {\n return template.text;\n }\n\n // Template with substitutions: `head ${expr} middle ${expr} tail`\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 * Extract SQL location from a tagged template expression\n */\nfunction extractSqlLocation(\n node: ts.TaggedTemplateExpression,\n sourceFile: ts.SourceFile,\n): SqlLocation {\n const start = sourceFile.getLineAndCharacterOfPosition(\n node.template.getStart(),\n );\n const end = sourceFile.getLineAndCharacterOfPosition(node.template.getEnd());\n\n return {\n id: `${sourceFile.fileName}:${start.line + 1}:${start.character + 1}`,\n file: sourceFile.fileName,\n line: start.line + 1, // 1-based\n column: start.character + 1, // 1-based\n endLine: end.line + 1,\n endColumn: end.character + 1,\n templateText: extractTemplateText(node.template),\n };\n}\n\n/**\n * Extract all SQL locations from a single source file.\n * Uses the TypeChecker to verify that the `sql` tag comes from moose-lib.\n */\nexport function extractSqlLocations(\n sourceFile: ts.SourceFile,\n typeChecker: ts.TypeChecker,\n): SqlLocation[] {\n const locations: SqlLocation[] = [];\n\n function visit(node: ts.Node): void {\n if (\n ts.isTaggedTemplateExpression(node) &&\n isMooseLibSqlTag(node, typeChecker)\n ) {\n locations.push(extractSqlLocation(node, sourceFile));\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return locations;\n}\n\n/**\n * Extract SQL locations from all source files (for initial scan).\n * Iterates through all source files in the program and collects SQL templates.\n */\nexport function extractAllSqlLocations(\n sourceFiles: readonly ts.SourceFile[],\n typeChecker: ts.TypeChecker,\n): SqlLocation[] {\n const allLocations: SqlLocation[] = [];\n\n for (const sourceFile of sourceFiles) {\n const locations = extractSqlLocations(sourceFile, typeChecker);\n allLocations.push(...locations);\n }\n\n return allLocations;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAAe;AAQf,SAAS,iBACP,MACA,aACS;AACT,QAAM,MAAM,KAAK;AAGjB,MAAI,CAAC,kBAAAA,QAAG,aAAa,GAAG,KAAK,IAAI,SAAS,OAAO;AAC/C,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,YAAY,oBAAoB,GAAG;AAClD,MAAI,QAAQ,cAAc,QAAQ;AAEhC,UAAM,iBAAiB,OAAO,aAAa,KAAK,CAAC,SAAS;AACxD,YAAM,aAAa,KAAK,cAAc;AACtC,YAAM,WAAW,WAAW;AAC5B,aACE,SAAS,SAAS,WAAW,KAC7B,SAAS,SAAS,oBAAoB,KACtC,SAAS,SAAS,mBAAmB,KACrC,SAAS,SAAS,YAAY;AAAA,IAElC,CAAC;AACD,QAAI,gBAAgB;AAClB,aAAO;AAAA,IACT;AAAA,EACF;AAIA,SAAO;AACT;AAOA,SAAS,oBAAoB,UAAsC;AACjE,MAAI,kBAAAA,QAAG,gCAAgC,QAAQ,GAAG;AAChD,WAAO,SAAS;AAAA,EAClB;AAGA,MAAI,OAAO,SAAS,KAAK;AACzB,aAAW,QAAQ,SAAS,eAAe;AACzC,YAAQ,UAAU,KAAK,QAAQ,IAAI;AAAA,EACrC;AACA,SAAO;AACT;AAKA,SAAS,mBACP,MACA,YACa;AACb,QAAM,QAAQ,WAAW;AAAA,IACvB,KAAK,SAAS,SAAS;AAAA,EACzB;AACA,QAAM,MAAM,WAAW,8BAA8B,KAAK,SAAS,OAAO,CAAC;AAE3E,SAAO;AAAA,IACL,IAAI,GAAG,WAAW,QAAQ,IAAI,MAAM,OAAO,CAAC,IAAI,MAAM,YAAY,CAAC;AAAA,IACnE,MAAM,WAAW;AAAA,IACjB,MAAM,MAAM,OAAO;AAAA;AAAA,IACnB,QAAQ,MAAM,YAAY;AAAA;AAAA,IAC1B,SAAS,IAAI,OAAO;AAAA,IACpB,WAAW,IAAI,YAAY;AAAA,IAC3B,cAAc,oBAAoB,KAAK,QAAQ;AAAA,EACjD;AACF;AAMO,SAAS,oBACd,YACA,aACe;AACf,QAAM,YAA2B,CAAC;AAElC,WAAS,MAAM,MAAqB;AAClC,QACE,kBAAAA,QAAG,2BAA2B,IAAI,KAClC,iBAAiB,MAAM,WAAW,GAClC;AACA,gBAAU,KAAK,mBAAmB,MAAM,UAAU,CAAC;AAAA,IACrD;AACA,sBAAAA,QAAG,aAAa,MAAM,KAAK;AAAA,EAC7B;AAEA,QAAM,UAAU;AAChB,SAAO;AACT;AAMO,SAAS,uBACd,aACA,aACe;AACf,QAAM,eAA8B,CAAC;AAErC,aAAW,cAAc,aAAa;AACpC,UAAM,YAAY,oBAAoB,YAAY,WAAW;AAC7D,iBAAa,KAAK,GAAG,SAAS;AAAA,EAChC;AAEA,SAAO;AACT;","names":["ts"]}
|