Skip to content

Commit e7a8352

Browse files
committed
refactor and code
1 parent c4e6c6c commit e7a8352

13 files changed

Lines changed: 2472 additions & 124 deletions

demo/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { acceptCompletion } from "@codemirror/autocomplete";
22
import { keywordCompletionSource, PostgreSQL, sql } from "@codemirror/lang-sql";
3+
import type { EditorState } from "@codemirror/state";
34
import { keymap } from "@codemirror/view";
4-
55
import { basicSetup, EditorView } from "codemirror";
66
import { cteCompletionSource } from "../src/sql/cte-completion-source.js";
77
import { sqlExtension } from "../src/sql/extension.js";
88
import { DefaultSqlTooltipRenders } from "../src/sql/hover.js";
9+
import { NodeSqlParser } from "../src/sql/parser.js";
910
import { type Schema, tableTooltipRenderer } from "./custom-renderers.js";
1011

1112
// Default SQL content for the demo
@@ -147,6 +148,15 @@ const getKeywordDocs = async () => {
147148

148149
// Initialize the SQL editor
149150
function initializeEditor() {
151+
const parser = new NodeSqlParser({
152+
getParserOptions: (_state: EditorState) => {
153+
return {
154+
database: "PostgreSQL",
155+
};
156+
},
157+
schema: schema,
158+
});
159+
150160
const extensions = [
151161
basicSetup,
152162
EditorView.lineWrapping,
@@ -157,8 +167,7 @@ function initializeEditor() {
157167
schema: schema,
158168
// Enable uppercase keywords for more traditional SQL style
159169
upperCaseKeywords: true,
160-
keywordCompletion: (label, type) => {
161-
// console.log("label", label, type);
170+
keywordCompletion: (label, _type) => {
162171
return {
163172
label,
164173
keyword: label,
@@ -182,13 +191,15 @@ function initializeEditor() {
182191
// Linter extension configuration
183192
linterConfig: {
184193
delay: 250, // Delay before running validation
194+
parser,
185195
},
186196

187197
// Gutter extension configuration
188198
gutterConfig: {
189199
backgroundColor: "#3b82f6", // Blue for current statement
190200
errorBackgroundColor: "#ef4444", // Red for invalid statements
191201
hideWhenNotFocused: true, // Hide gutter when editor loses focus
202+
parser,
192203
},
193204
// Hover extension configuration
194205
enableHover: true, // Enable hover tooltips
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { EditorState, Text } from "@codemirror/state";
2+
import { EditorView } from "@codemirror/view";
3+
import { beforeEach, describe, expect, it } from "vitest";
4+
import { sqlLinter } from "../diagnostics.js";
5+
import { NodeSqlParser } from "../parser.js";
6+
import { sqlStructureGutter } from "../structure-extension.js";
7+
8+
describe("Gutter and Diagnostics Integration", () => {
9+
let parser: NodeSqlParser;
10+
11+
beforeEach(() => {
12+
parser = new NodeSqlParser({
13+
schema: {
14+
users: ["id", "name", "email", "active"],
15+
posts: ["id", "title", "user_id"],
16+
orders: ["id", "customer_id", "order_date", "total_amount"],
17+
},
18+
});
19+
});
20+
21+
const createState = (content: string) => {
22+
return EditorState.create({
23+
doc: Text.of(content.split("\n")),
24+
extensions: [sqlLinter({ parser }), sqlStructureGutter({ parser })],
25+
});
26+
};
27+
28+
describe("error detection consistency", () => {
29+
it("should detect syntax errors consistently between gutter and diagnostics", async () => {
30+
const content = "SELECT * FROM;";
31+
32+
// Test diagnostics directly
33+
const errors = await parser.validateSql(content, { state: createState(content) });
34+
35+
expect(errors).toHaveLength(1);
36+
expect(errors[0].severity).toBe("error");
37+
expect(errors[0].message).toContain("unexpected token");
38+
});
39+
40+
it("should detect schema validation errors consistently", async () => {
41+
const content = "SELECT invalid_column FROM users;";
42+
43+
const errors = await parser.validateSql(content, { state: createState(content) });
44+
45+
expect(errors).toHaveLength(1);
46+
expect(errors[0].severity).toBe("error");
47+
expect(errors[0].message).toContain("does not exist");
48+
});
49+
50+
it("should detect missing table errors consistently", async () => {
51+
const content = "SELECT * FROM non_existent_table;";
52+
53+
const errors = await parser.validateSql(content, { state: createState(content) });
54+
55+
expect(errors.length).toBeGreaterThan(0);
56+
expect(errors.every((e) => e.severity === "error")).toBe(true);
57+
expect(errors.some((e) => e.message.includes("does not exist"))).toBe(true);
58+
});
59+
60+
it("should handle mixed valid and invalid statements", async () => {
61+
const content = `
62+
SELECT * FROM users; -- Valid
63+
SELECT * FROM; -- Invalid
64+
INSERT INTO users (name) VALUES ('John'); -- Valid
65+
UPDATE SET name = 'test'; -- Invalid
66+
`;
67+
68+
const errors = await parser.validateSql(content, { state: createState(content) });
69+
70+
// Should have errors for the invalid statements
71+
expect(errors.length).toBeGreaterThan(0);
72+
expect(errors.every((e) => e.severity === "error")).toBe(true);
73+
});
74+
75+
it("should detect errors in complex queries", async () => {
76+
const content = `
77+
SELECT u.id,
78+
u.name,
79+
u.email
80+
FROM users u
81+
WHERE u.active = true
82+
AND u.invalid_column > 100;
83+
`;
84+
85+
const errors = await parser.validateSql(content, { state: createState(content) });
86+
87+
expect(errors).toHaveLength(1);
88+
expect(errors[0].severity).toBe("error");
89+
expect(errors[0].message).toContain("does not exist");
90+
});
91+
92+
it("should detect errors in JOIN clauses", async () => {
93+
const content = `
94+
SELECT u.name, p.title
95+
FROM users u
96+
JOIN posts p ON u.id = p.user_id
97+
JOIN non_existent_table n ON p.id = n.post_id;
98+
`;
99+
100+
const errors = await parser.validateSql(content, { state: createState(content) });
101+
102+
expect(errors.length).toBeGreaterThan(0);
103+
expect(errors.every((e) => e.severity === "error")).toBe(true);
104+
expect(errors.some((e) => e.message.includes("does not exist"))).toBe(true);
105+
});
106+
107+
it("should detect errors in subqueries", async () => {
108+
const content = `
109+
SELECT customer_id,
110+
order_date,
111+
total_amount
112+
FROM orders
113+
WHERE order_date >= '2024-01-01'
114+
AND total_amount > (
115+
SELECT AVG(total_amount) * 0.8
116+
FROM non_existent_table
117+
WHERE YEAR(order_date) = 2024
118+
);
119+
`;
120+
121+
const errors = await parser.validateSql(content, { state: createState(content) });
122+
123+
expect(errors).toHaveLength(1);
124+
expect(errors[0].severity).toBe("error");
125+
expect(errors[0].message).toContain("does not exist");
126+
});
127+
128+
it("should handle valid statements without errors", async () => {
129+
const content = `
130+
SELECT id, name, email FROM users WHERE active = true;
131+
INSERT INTO users (name, email) VALUES ('John', 'john@example.com');
132+
UPDATE users SET active = false WHERE id = 1;
133+
`;
134+
135+
const errors = await parser.validateSql(content, { state: createState(content) });
136+
137+
// All statements should be valid
138+
expect(errors).toHaveLength(0);
139+
});
140+
141+
it("should handle qualified column references correctly", async () => {
142+
const content = `
143+
SELECT u.id, u.name, p.title
144+
FROM users u
145+
JOIN posts p ON u.id = p.user_id;
146+
`;
147+
148+
const errors = await parser.validateSql(content, { state: createState(content) });
149+
150+
// Should be valid with proper schema
151+
expect(errors).toHaveLength(0);
152+
});
153+
154+
it("should detect errors in qualified column references", async () => {
155+
const content = `
156+
SELECT u.invalid_column, p.title
157+
FROM users u
158+
JOIN posts p ON u.id = p.user_id;
159+
`;
160+
161+
const errors = await parser.validateSql(content, { state: createState(content) });
162+
163+
expect(errors).toHaveLength(1);
164+
expect(errors[0].severity).toBe("error");
165+
expect(errors[0].message).toContain("does not exist");
166+
});
167+
});
168+
169+
describe("gutter marker behavior", () => {
170+
it("should create gutter extensions with error configuration", () => {
171+
const gutterExtensions = sqlStructureGutter({
172+
errorBackgroundColor: "#ef4444",
173+
showInvalid: true,
174+
parser,
175+
});
176+
177+
expect(Array.isArray(gutterExtensions)).toBe(true);
178+
expect(gutterExtensions.length).toBe(4);
179+
});
180+
181+
it("should handle gutter with schema validation", () => {
182+
const gutterExtensions = sqlStructureGutter({
183+
errorBackgroundColor: "#ef4444",
184+
showInvalid: true,
185+
parser,
186+
});
187+
188+
expect(Array.isArray(gutterExtensions)).toBe(true);
189+
expect(gutterExtensions.length).toBe(4);
190+
});
191+
192+
it("should configure gutter to show invalid statements", () => {
193+
const gutterExtensions = sqlStructureGutter({
194+
showInvalid: true,
195+
errorBackgroundColor: "#ff0000",
196+
parser,
197+
});
198+
199+
expect(Array.isArray(gutterExtensions)).toBe(true);
200+
expect(gutterExtensions.length).toBe(4);
201+
});
202+
});
203+
});

0 commit comments

Comments
 (0)