Skip to content

Commit 575c72e

Browse files
authored
fix: lint diagnostics offset when statements contain comments (#162)
Parse the original statement text (comments included) instead of the comment-stripped version. node-sql-parser handles comments natively and reports positions relative to the original text, so stripping comments before parsing shifted error lines/columns in the document. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Fixes incorrect lint diagnostic positions when SQL statements include comments by parsing the original text (comments included). This aligns error lines/columns with the document, leveraging `node-sql-parser`’s comment-aware positions. - **Bug Fixes** - Parse the original statement content in `SqlStructureAnalyzer` and use stripped content only to detect emptiness and determine statement type. - Added tests for line comments, multi-line comments, and inline comments to ensure correct offsets and no false positives for valid statements. <sup>Written for commit 6e3b4f5. Summary will update on new commits.</sup> <a href="https://cubic.dev/pr/marimo-team/codemirror-sql/pull/162?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
1 parent 428d0fb commit 575c72e

3 files changed

Lines changed: 65 additions & 4 deletions

File tree

src/sql/__tests__/diagnostics.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,66 @@ describe("lint source", () => {
163163
}
164164
});
165165

166+
it("should position diagnostics correctly when a line comment precedes the statement", async () => {
167+
const doc = [
168+
"-- Valid queries (no errors):",
169+
"SELECT id, name, email",
170+
"FROM users",
171+
"WHERE active == true",
172+
"ORDER BY created_at DESC;",
173+
].join("\n");
174+
const diagnostics = await lint(doc);
175+
176+
expect(diagnostics).toHaveLength(1);
177+
const text = Text.of(doc.split("\n"));
178+
// The error is at the "==" operator on line 4, not shifted up by the comment
179+
expect(text.lineAt(diagnostics[0].from).number).toBe(4);
180+
expect(diagnostics[0].from).toBe(doc.indexOf("==") + 1);
181+
});
182+
183+
it("should position diagnostics correctly when a multi-line comment precedes the statement", async () => {
184+
const doc = [
185+
"/* header",
186+
"comment */",
187+
"SELECT id",
188+
"FROM users",
189+
"WHERE active == true;",
190+
].join("\n");
191+
const diagnostics = await lint(doc);
192+
193+
expect(diagnostics).toHaveLength(1);
194+
const text = Text.of(doc.split("\n"));
195+
expect(text.lineAt(diagnostics[0].from).number).toBe(5);
196+
expect(diagnostics[0].from).toBe(doc.indexOf("==") + 1);
197+
});
198+
199+
it("should position diagnostics correctly when a comment appears inside the statement", async () => {
200+
const doc = [
201+
"SELECT id",
202+
"-- pick the table",
203+
"FROM users",
204+
"WHERE active == true;",
205+
].join("\n");
206+
const diagnostics = await lint(doc);
207+
208+
expect(diagnostics).toHaveLength(1);
209+
const text = Text.of(doc.split("\n"));
210+
expect(text.lineAt(diagnostics[0].from).number).toBe(4);
211+
expect(diagnostics[0].from).toBe(doc.indexOf("==") + 1);
212+
});
213+
214+
it("should not report diagnostics for valid statements with comments", async () => {
215+
const doc = [
216+
"-- fetch users",
217+
"SELECT id /* inline */, name",
218+
"FROM users;",
219+
"/* another",
220+
" comment */",
221+
"SELECT 2;",
222+
].join("\n");
223+
expect(await lint(doc)).toEqual([]);
224+
});
225+
166226
it("should reuse errors from a provided structure analyzer without re-validating", async () => {
167227
const parser = new NodeSqlParser();
168228
const validateSpy = vi.spyOn(parser, "validateSql");

src/sql/diagnostics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function convertStatementErrorToDiagnostic(
7777
from = line.from + Math.max(0, error.column - 1);
7878
}
7979
// Clamp within the statement's range in case the parser's reported
80-
// position drifts (e.g. comments are stripped before parsing)
80+
// position drifts
8181
from = Math.max(stmt.from, Math.min(from, stmt.to));
8282

8383
return {

src/sql/structure-analyzer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export class SqlStructureAnalyzer {
104104
const fromLine = state.doc.lineAt(from);
105105
const toLine = state.doc.lineAt(to);
106106

107-
// Strip comments from the statement content
107+
// Strip comments to detect comment-only parts and determine the type
108108
const strippedContent = this.stripComments(trimmedPart);
109109

110110
// Skip if the statement is empty after stripping comments
@@ -113,8 +113,9 @@ export class SqlStructureAnalyzer {
113113
continue;
114114
}
115115

116-
// Parse the statement to determine validity and type (use stripped content)
117-
const parseResult = await this.parser.parse(strippedContent, { state });
116+
// Parse the original content (comments included) so error positions
117+
// reported by the parser line up with the document
118+
const parseResult = await this.parser.parse(trimmedPart, { state });
118119
const type = this.determineStatementType(strippedContent);
119120

120121
// Remove trailing semicolon from content for cleaner display

0 commit comments

Comments
 (0)