Skip to content

Commit 7a1105e

Browse files
committed
feat: add parseWithComments with correct span serialization
Uses js_sys::Object to build the result directly instead of double-serializing through serde_json::Value. Statements and comments are both serialized via serde_wasm_bindgen separately.
1 parent 8769d5a commit 7a1105e

8 files changed

Lines changed: 115 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ serde = { version = "1.0", features = ["derive"] }
1616
serde_json = "1.0"
1717
serde-wasm-bindgen = "0.6"
1818
console_error_panic_hook = "0.1"
19+
js-sys = "0.3"
1920

2021
[profile.release]
2122
opt-level = "s"

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
// Parser
7-
export { Parser, init, parse, validate, format } from './parser.js';
7+
export { Parser, init, parse, parseWithComments, validate, format } from './parser.js';
88
export type { ParserOptions, DialectInput } from './parser.js';
99

1010
// Dialects

src/lib.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use sqlparser::dialect::{
44
GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect,
55
RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect,
66
};
7+
use sqlparser::ast::comments::{Comment as SqlComment, CommentWithSpan};
78
use sqlparser::parser::Parser;
89
use wasm_bindgen::prelude::*;
910

@@ -163,6 +164,69 @@ pub fn get_supported_dialects() -> JsValue {
163164
serde_wasm_bindgen::to_value(&dialects).unwrap()
164165
}
165166

167+
/// A serializable source comment
168+
#[derive(Serialize)]
169+
#[serde(rename_all = "camelCase")]
170+
pub struct SerializedComment {
171+
pub comment_type: String,
172+
pub content: String,
173+
#[serde(skip_serializing_if = "Option::is_none")]
174+
pub prefix: Option<String>,
175+
pub start_line: u64,
176+
pub start_column: u64,
177+
pub end_line: u64,
178+
pub end_column: u64,
179+
}
180+
181+
impl From<&CommentWithSpan> for SerializedComment {
182+
fn from(c: &CommentWithSpan) -> Self {
183+
let (comment_type, content, prefix) = match &c.comment {
184+
SqlComment::SingleLine { content, prefix } => {
185+
("singleLine".to_string(), content.clone(), Some(prefix.clone()))
186+
}
187+
SqlComment::MultiLine(content) => {
188+
("multiLine".to_string(), content.clone(), None)
189+
}
190+
};
191+
SerializedComment {
192+
comment_type, content, prefix,
193+
start_line: c.span.start.line,
194+
start_column: c.span.start.column,
195+
end_line: c.span.end.line,
196+
end_column: c.span.end.column,
197+
}
198+
}
199+
}
200+
201+
/// Parse SQL and return both AST and source comments
202+
#[wasm_bindgen]
203+
pub fn parse_sql_with_comments(dialect: &str, sql: &str) -> Result<JsValue, JsValue> {
204+
let dialect_impl = get_dialect(dialect);
205+
let (statements, comments) =
206+
Parser::parse_sql_with_comments(dialect_impl.as_ref(), sql).map_err(|e| {
207+
let error = ParseError {
208+
message: e.to_string(),
209+
line: None,
210+
column: None,
211+
};
212+
serde_wasm_bindgen::to_value(&error).unwrap_or(JsValue::from_str(&e.to_string()))
213+
})?;
214+
215+
let comments_vec: Vec<CommentWithSpan> = comments.into();
216+
let serialized_comments: Vec<SerializedComment> =
217+
comments_vec.iter().map(SerializedComment::from).collect();
218+
219+
// Build JS object manually to avoid double-serialization
220+
let obj = js_sys::Object::new();
221+
let stmts_val = serde_wasm_bindgen::to_value(&statements)
222+
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
223+
let comments_val = serde_wasm_bindgen::to_value(&serialized_comments)
224+
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
225+
js_sys::Reflect::set(&obj, &"statements".into(), &stmts_val).unwrap();
226+
js_sys::Reflect::set(&obj, &"comments".into(), &comments_val).unwrap();
227+
Ok(obj.into())
228+
}
229+
166230
/// Validate SQL syntax without returning the full AST
167231
#[wasm_bindgen]
168232
pub fn validate_sql(dialect: &str, sql: &str) -> Result<bool, JsValue> {

src/parser.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Dialect, DialectName } from './dialects.js';
22
import { dialectFromString, GenericDialect } from './dialects.js';
33
import { ParserError } from './types/errors.js';
44
import type { Statement } from './types/ast.js';
5+
import type { SourceComment, ParseWithCommentsResult } from './types/comments.js';
56
import { getWasmModule } from './wasm.js';
67

78
export { init } from './wasm.js';
@@ -80,13 +81,28 @@ export class Parser {
8081
}
8182
}
8283

84+
/** Parse SQL and return both AST and source comments */
85+
parseWithComments(sql: string): ParseWithCommentsResult<Statement> {
86+
const wasm = getWasmModule();
87+
try {
88+
return wasm.parse_sql_with_comments(this.dialect.name, sql) as ParseWithCommentsResult<Statement>;
89+
} catch (error) {
90+
throw ParserError.fromWasmError(error);
91+
}
92+
}
93+
8394
// Static methods
8495

8596
/** Parse SQL into AST */
8697
static parse(sql: string, dialect: DialectInput = 'generic'): Statement[] {
8798
return new Parser(resolveDialect(dialect)).parse(sql);
8899
}
89100

101+
/** Parse SQL and return both AST and source comments */
102+
static parseWithComments(sql: string, dialect: DialectInput = 'generic'): ParseWithCommentsResult<Statement> {
103+
return new Parser(resolveDialect(dialect)).parseWithComments(sql);
104+
}
105+
90106
/** Parse SQL and return AST as JSON string */
91107
static parseToJson(sql: string, dialect: DialectInput = 'generic'): string {
92108
const wasm = getWasmModule();
@@ -145,6 +161,13 @@ export function parse(sql: string, dialect: DialectInput = 'generic'): Statement
145161
return Parser.parse(sql, dialect);
146162
}
147163

164+
/**
165+
* Parse SQL and return both AST and source comments
166+
*/
167+
export function parseWithComments(sql: string, dialect: DialectInput = 'generic'): ParseWithCommentsResult<Statement> {
168+
return Parser.parseWithComments(sql, dialect);
169+
}
170+
148171
/**
149172
* Validate SQL syntax
150173
* @throws ParserError if SQL is invalid

src/types/comments.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** A source code comment extracted from parsed SQL */
2+
export interface SourceComment {
3+
/** "singleLine" for -- comments, "multiLine" for block comments */
4+
commentType: 'singleLine' | 'multiLine'
5+
/** The comment text content (excluding markers) */
6+
content: string
7+
/** For single-line comments, the prefix (e.g. "--", "#") */
8+
prefix?: string
9+
/** Start line (1-based) */
10+
startLine: number
11+
/** Start column (1-based) */
12+
startColumn: number
13+
/** End line (1-based) */
14+
endLine: number
15+
/** End column (1-based) */
16+
endColumn: number
17+
}
18+
19+
/** Result of parsing SQL with comments */
20+
export interface ParseWithCommentsResult<T> {
21+
statements: T[]
22+
comments: SourceComment[]
23+
}

src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './ast.js';
22
export * from './errors.js';
3+
export * from './comments.js';

src/wasm.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WasmInitError } from './types/errors.js';
44
export interface WasmModule {
55
parse_sql: (dialect: string, sql: string) => unknown;
66
parse_sql_with_options: (dialect: string, sql: string, options: unknown) => unknown;
7+
parse_sql_with_comments: (dialect: string, sql: string) => unknown;
78
parse_sql_to_json_string: (dialect: string, sql: string) => string;
89
parse_sql_to_string: (dialect: string, sql: string) => string;
910
format_sql: (dialect: string, sql: string) => string;

0 commit comments

Comments
 (0)