Skip to content

Commit f2b110a

Browse files
committed
refactor and code
1 parent 1e1e455 commit f2b110a

13 files changed

Lines changed: 2468 additions & 121 deletions

demo/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { NodeSqlParser } from "../src/index.js";
77
import { cteCompletionSource } from "../src/sql/cte-completion-source.js";
88
import { sqlExtension } from "../src/sql/extension.js";
99
import { DefaultSqlTooltipRenders } from "../src/sql/hover.js";
10+
import { NodeSqlParser } from "../src/sql/parser.js";
1011
import { type Schema, tableTooltipRenderer } from "./custom-renderers.js";
1112

1213
// Default SQL content for the demo
@@ -168,6 +169,15 @@ function initializeEditor() {
168169
},
169170
});
170171

172+
const parser = new NodeSqlParser({
173+
getParserOptions: (_state: EditorState) => {
174+
return {
175+
database: "PostgreSQL",
176+
};
177+
},
178+
schema: schema,
179+
});
180+
171181
const extensions = [
172182
basicSetup,
173183
EditorView.lineWrapping,
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)