@thornfe/jql2iql 0.0.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/index.mjs ADDED
@@ -0,0 +1,1526 @@
1
+ import { CharStreams, CommonTokenStream } from "antlr4ts";
2
+ import { JQLLexer, JQLParser } from "@atlaskit/jql-parser";
3
+ import { TerminalNode } from "antlr4ts/tree/TerminalNode";
4
+ import { RuleNode } from "antlr4ts/tree/RuleNode";
5
+
6
+ //#region src/constants.ts
7
+ /**
8
+ * JQL 解析树节点类型常量
9
+ */
10
+ const JQL_CONTEXT = {
11
+ TERMINAL_CLAUSE: "JqlTerminalClauseContext",
12
+ NOT_CLAUSE: "JqlNotClauseContext",
13
+ OR_CLAUSE: "JqlOrClauseContext",
14
+ AND_CLAUSE: "JqlAndClauseContext",
15
+ LIST: "JqlListContext",
16
+ FUNCTION: "JqlFunctionContext",
17
+ EMPTY: "JqlEmptyContext",
18
+ VALUE: "JqlValueContext",
19
+ OPERAND: "JqlOperandContext",
20
+ QUERY: "JqlQueryContext",
21
+ WHERE: "JqlWhereContext",
22
+ ORDER_BY: "JqlOrderByContext",
23
+ SEARCH_SORT: "JqlSearchSortContext",
24
+ FUNCTION_NAME: "JqlFunctionNameContext",
25
+ ARGUMENT_LIST: "JqlArgumentListContext",
26
+ ARGUMENT: "JqlArgumentContext",
27
+ EQUALS_CLAUSE: "JqlEqualsClauseContext",
28
+ COMPARISON_CLAUSE: "JqlComparisonClauseContext",
29
+ IN_CLAUSE: "JqlInClauseContext",
30
+ LIKE_CLAUSE: "JqlLikeClauseContext",
31
+ IS_CLAUSE: "JqlIsClauseContext",
32
+ STRING: "JqlStringContext",
33
+ NUMBER: "JqlNumberContext"
34
+ };
35
+ /**
36
+ * 包装节点类型(透明传递给子节点)
37
+ */
38
+ const WRAPPER_CONTEXTS = [
39
+ "JqlStringContext",
40
+ "JqlNumberContext",
41
+ "JqlNonNumberFieldContext",
42
+ "JqlListStartContext",
43
+ "JqlListEndContext",
44
+ "JqlEqualsOperatorContext",
45
+ "JqlComparisonOperatorContext",
46
+ "JqlInOperatorContext",
47
+ "JqlLikeOperatorContext",
48
+ "JqlFunctionNameContext",
49
+ "JqlArgumentContext"
50
+ ];
51
+ /**
52
+ * 逻辑操作符常量
53
+ */
54
+ const LOGICAL_OPERATORS = {
55
+ AND: "AND",
56
+ OR: "OR",
57
+ NOT: "NOT"
58
+ };
59
+ /**
60
+ * IQL 逻辑操作符(小写)
61
+ */
62
+ const IQL_LOGICAL_OPERATORS = {
63
+ AND: "and",
64
+ OR: "or",
65
+ NOT: "not"
66
+ };
67
+ /**
68
+ * 比较操作符常量
69
+ */
70
+ const COMPARISON_OPERATORS = {
71
+ EQUAL: "=",
72
+ NOT_EQUAL: "!=",
73
+ GREATER_THAN: ">",
74
+ GREATER_THAN_OR_EQUAL: ">=",
75
+ LESS_THAN: "<",
76
+ LESS_THAN_OR_EQUAL: "<=",
77
+ LIKE: "~",
78
+ NOT_LIKE: "!~"
79
+ };
80
+ /**
81
+ * 集合操作符常量
82
+ */
83
+ const SET_OPERATORS = {
84
+ IN: "in",
85
+ NOT_IN: "not in"
86
+ };
87
+ /**
88
+ * 空值操作符常量
89
+ */
90
+ const NULL_OPERATORS = {
91
+ IS: "is",
92
+ IS_NOT: "is not"
93
+ };
94
+ /**
95
+ * 空值关键字
96
+ */
97
+ const NULL_KEYWORDS = {
98
+ NULL: "null",
99
+ NULL_UPPER: "NULL",
100
+ EMPTY: "EMPTY",
101
+ EMPTY_LOWER: "empty"
102
+ };
103
+ /**
104
+ * 特殊函数名称
105
+ */
106
+ const FUNCTION_NAMES = {
107
+ CURRENT_USER: "currentUser",
108
+ MEMBERS_OF: "membersOf",
109
+ CASCADE_OPTION: "cascadeOption"
110
+ };
111
+ /**
112
+ * SQL 子句关键字
113
+ */
114
+ const SQL_CLAUSES = {
115
+ ORDER_BY: "order by",
116
+ ASC: "asc",
117
+ DESC: "desc"
118
+ };
119
+ /**
120
+ * AST 节点类型
121
+ */
122
+ const AST_NODE_TYPES = {
123
+ BINARY_EXPRESSION: "BinaryExpression",
124
+ LOGICAL_EXPRESSION: "LogicalExpression",
125
+ JQL_QUERY: "JQLQuery",
126
+ ORDER_BY_CLAUSE: "OrderByClause",
127
+ LITERAL: "Literal",
128
+ FUNCTION: "Function",
129
+ LIST: "List"
130
+ };
131
+ /**
132
+ * 支持的字段类型列表
133
+ */
134
+ const SUPPORTED_FIELD_TYPES = {
135
+ createdBy: "createdBy",
136
+ updatedBy: "updatedBy",
137
+ Assignee: "Assignee",
138
+ Reporter: "Reporter",
139
+ User: "User",
140
+ Status: "Status",
141
+ Version: "Version",
142
+ CustomVersion: "CustomVersion",
143
+ Sprint: "Sprint",
144
+ Priority: "Priority",
145
+ Workspace: "Workspace",
146
+ ItemType: "ItemType",
147
+ Name: "Name",
148
+ Text: "Text",
149
+ LongText: "LongText",
150
+ Key: "Key",
151
+ Number: "Number",
152
+ Integer: "Integer",
153
+ Float: "Float",
154
+ StoryPoint: "StoryPoint",
155
+ Date: "Date",
156
+ createdAt: "createdAt",
157
+ updatedAt: "updatedAt",
158
+ UserGroup: "UserGroup",
159
+ Tree: "Tree",
160
+ Tag: "Tag",
161
+ Cascade: "Cascade",
162
+ Checkbox: "Checkbox",
163
+ Dropdown: "Dropdown",
164
+ Radio: "Radio"
165
+ };
166
+
167
+ //#endregion
168
+ //#region src/jql-to-ast/parser-utils.ts
169
+ const OPERATOR_SET = new Set([
170
+ COMPARISON_OPERATORS.EQUAL,
171
+ COMPARISON_OPERATORS.NOT_EQUAL,
172
+ COMPARISON_OPERATORS.GREATER_THAN,
173
+ COMPARISON_OPERATORS.LESS_THAN,
174
+ COMPARISON_OPERATORS.GREATER_THAN_OR_EQUAL,
175
+ COMPARISON_OPERATORS.LESS_THAN_OR_EQUAL,
176
+ COMPARISON_OPERATORS.LIKE,
177
+ COMPARISON_OPERATORS.NOT_LIKE,
178
+ SET_OPERATORS.IN,
179
+ SET_OPERATORS.NOT_IN
180
+ ]);
181
+ new Set([
182
+ LOGICAL_OPERATORS.AND.toLowerCase(),
183
+ LOGICAL_OPERATORS.OR.toLowerCase(),
184
+ LOGICAL_OPERATORS.NOT.toLowerCase()
185
+ ]);
186
+ const CLAUSE_OPERATORS = {
187
+ [JQL_CONTEXT.EQUALS_CLAUSE]: [COMPARISON_OPERATORS.EQUAL, COMPARISON_OPERATORS.NOT_EQUAL],
188
+ [JQL_CONTEXT.COMPARISON_CLAUSE]: [
189
+ COMPARISON_OPERATORS.GREATER_THAN,
190
+ COMPARISON_OPERATORS.LESS_THAN,
191
+ COMPARISON_OPERATORS.GREATER_THAN_OR_EQUAL,
192
+ COMPARISON_OPERATORS.LESS_THAN_OR_EQUAL
193
+ ],
194
+ [JQL_CONTEXT.IN_CLAUSE]: [SET_OPERATORS.IN, SET_OPERATORS.NOT_IN],
195
+ [JQL_CONTEXT.LIKE_CLAUSE]: [COMPARISON_OPERATORS.LIKE, COMPARISON_OPERATORS.NOT_LIKE],
196
+ [JQL_CONTEXT.IS_CLAUSE]: [NULL_OPERATORS.IS, NULL_OPERATORS.IS_NOT]
197
+ };
198
+ /**
199
+ * 遍历 JQL 解析树
200
+ * @param tree 解析树根节点
201
+ * @param callback 遍历回调函数,返回 false 可以跳过子节点
202
+ * @param depth 当前深度(内部使用)
203
+ * @param parent 父节点(内部使用)
204
+ */
205
+ function traverseJQLTree(tree, callback, depth = 0, parent) {
206
+ if (callback(tree, depth, parent) === false) return;
207
+ const childCount = tree.childCount;
208
+ for (let i = 0; i < childCount; i++) traverseJQLTree(tree.getChild(i), callback, depth + 1, tree);
209
+ }
210
+ /**
211
+ * 获取节点信息
212
+ */
213
+ function getNodeInfo(node) {
214
+ if (node instanceof TerminalNode) {
215
+ var _node$symbol$type;
216
+ return {
217
+ type: "terminal",
218
+ text: node.text,
219
+ tokenType: (_node$symbol$type = node.symbol.type) === null || _node$symbol$type === void 0 ? void 0 : _node$symbol$type.toString()
220
+ };
221
+ } else if (node instanceof RuleNode) return {
222
+ type: "rule",
223
+ text: node.text,
224
+ ruleName: node.constructor.name
225
+ };
226
+ return {
227
+ type: "rule",
228
+ text: node.text
229
+ };
230
+ }
231
+ /**
232
+ * 获取所有子节点
233
+ */
234
+ function getChildren(node) {
235
+ const children = [];
236
+ for (let i = 0; i < node.childCount; i++) children.push(node.getChild(i));
237
+ return children;
238
+ }
239
+ /**
240
+ * 提取字段名或简单值
241
+ */
242
+ function extractFieldValue(node) {
243
+ if (!node) return null;
244
+ if (node instanceof TerminalNode) return node.text;
245
+ if (node instanceof RuleNode) {
246
+ const ruleName = node.constructor.name;
247
+ if (ruleName === JQL_CONTEXT.STRING || ruleName === JQL_CONTEXT.NUMBER) return node.text;
248
+ }
249
+ for (let i = 0; i < node.childCount; i++) {
250
+ const value = extractFieldValue(node.getChild(i));
251
+ if (value) return value;
252
+ }
253
+ return null;
254
+ }
255
+ /**
256
+ * 提取操作符
257
+ */
258
+ function extractOperator(node) {
259
+ var _constructor;
260
+ if (!node) return null;
261
+ if (node instanceof TerminalNode) {
262
+ const text = node.text;
263
+ const lowerText = text.toLowerCase();
264
+ return OPERATOR_SET.has(text) || OPERATOR_SET.has(lowerText) ? text : null;
265
+ }
266
+ const operators = CLAUSE_OPERATORS[((_constructor = node.constructor) === null || _constructor === void 0 ? void 0 : _constructor.name) || ""];
267
+ if (operators) return extractOperatorFromClause(node, operators);
268
+ for (let i = 0; i < node.childCount; i++) {
269
+ const result = extractOperator(node.getChild(i));
270
+ if (result) return result;
271
+ }
272
+ return null;
273
+ }
274
+ /**
275
+ * 从特定类型的子句中提取操作符
276
+ */
277
+ function extractOperatorFromClause(node, possibleOperators) {
278
+ let multiWordOps = null;
279
+ const singleWordOps = /* @__PURE__ */ new Set();
280
+ for (const op of possibleOperators) if (op.includes(" ")) {
281
+ if (!multiWordOps) multiWordOps = [];
282
+ multiWordOps.push(op);
283
+ } else singleWordOps.add(op.toLowerCase());
284
+ const tokens = [];
285
+ const collectTokens = (n) => {
286
+ for (let i = 0; i < n.childCount; i++) {
287
+ const child = n.getChild(i);
288
+ if (child instanceof TerminalNode) tokens.push(child.text.toLowerCase());
289
+ else collectTokens(child);
290
+ }
291
+ };
292
+ if (multiWordOps) {
293
+ collectTokens(node);
294
+ if (tokens.length > 1) for (const op of multiWordOps) {
295
+ const opTokens = op.toLowerCase().split(" ");
296
+ const opLen = opTokens.length;
297
+ for (let i = 0; i <= tokens.length - opLen; i++) {
298
+ let match = true;
299
+ for (let j = 0; j < opLen; j++) if (tokens[i + j] !== opTokens[j]) {
300
+ match = false;
301
+ break;
302
+ }
303
+ if (match) return op;
304
+ }
305
+ }
306
+ }
307
+ for (let i = 0; i < node.childCount; i++) {
308
+ const child = node.getChild(i);
309
+ if (child instanceof TerminalNode) {
310
+ const text = child.text;
311
+ const lowerText = text.toLowerCase();
312
+ if (singleWordOps.has(lowerText) || singleWordOps.has(text)) return text;
313
+ } else {
314
+ const result = extractOperatorFromClause(child, possibleOperators);
315
+ if (result) return result;
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+
321
+ //#endregion
322
+ //#region src/jql-to-ast/ast-builder.ts
323
+ const KEYWORDS = new Set([
324
+ LOGICAL_OPERATORS.AND,
325
+ LOGICAL_OPERATORS.OR,
326
+ LOGICAL_OPERATORS.NOT
327
+ ]);
328
+ const OPERATORS = new Set([
329
+ COMPARISON_OPERATORS.EQUAL,
330
+ COMPARISON_OPERATORS.NOT_EQUAL,
331
+ COMPARISON_OPERATORS.GREATER_THAN,
332
+ COMPARISON_OPERATORS.LESS_THAN,
333
+ COMPARISON_OPERATORS.GREATER_THAN_OR_EQUAL,
334
+ COMPARISON_OPERATORS.LESS_THAN_OR_EQUAL,
335
+ COMPARISON_OPERATORS.LIKE,
336
+ COMPARISON_OPERATORS.NOT_LIKE
337
+ ]);
338
+ const SKIP_TERMINALS = new Set([
339
+ "(",
340
+ ")",
341
+ ","
342
+ ]);
343
+ /**
344
+ * 将 JQL 解析树转换为精简的 AST
345
+ */
346
+ function buildAST(node) {
347
+ if (!node) return null;
348
+ const info = getNodeInfo(node);
349
+ if (node instanceof TerminalNode) {
350
+ const text = node.text;
351
+ if (KEYWORDS.has(text.toUpperCase()) || OPERATORS.has(text)) return null;
352
+ return {
353
+ type: "Literal",
354
+ value: text
355
+ };
356
+ }
357
+ const ruleName = info.ruleName || "";
358
+ if (ruleName === JQL_CONTEXT.TERMINAL_CLAUSE) return buildTerminalClauseAST(node);
359
+ if (ruleName === JQL_CONTEXT.NOT_CLAUSE) return buildNotClauseAST(node);
360
+ if (ruleName === JQL_CONTEXT.OR_CLAUSE) return buildOrClauseAST(node);
361
+ if (ruleName === JQL_CONTEXT.AND_CLAUSE) return buildAndClauseAST(node);
362
+ if (ruleName === JQL_CONTEXT.LIST) return buildListAST(node);
363
+ if (ruleName === JQL_CONTEXT.FUNCTION) return buildFunctionAST(node);
364
+ if (ruleName === JQL_CONTEXT.EMPTY) return {
365
+ type: "Literal",
366
+ value: "EMPTY"
367
+ };
368
+ if (ruleName === JQL_CONTEXT.VALUE) {
369
+ const value = extractFieldValue(node);
370
+ return value ? {
371
+ type: "Literal",
372
+ value
373
+ } : null;
374
+ }
375
+ if (ruleName === JQL_CONTEXT.OPERAND) {
376
+ const children$1 = getChildren(node);
377
+ for (let i = 0; i < children$1.length; i++) {
378
+ const result = buildAST(children$1[i]);
379
+ if (result) return result;
380
+ }
381
+ return null;
382
+ }
383
+ if (ruleName === JQL_CONTEXT.QUERY) return buildQueryAST(node);
384
+ if (ruleName === JQL_CONTEXT.WHERE || WRAPPER_CONTEXTS.includes(ruleName)) {
385
+ const children$1 = getChildren(node);
386
+ for (let i = 0; i < children$1.length; i++) {
387
+ const result = buildAST(children$1[i]);
388
+ if (result) return result;
389
+ }
390
+ return null;
391
+ }
392
+ const children = getChildren(node);
393
+ for (let i = 0; i < children.length; i++) {
394
+ const result = buildAST(children[i]);
395
+ if (result) return result;
396
+ }
397
+ return null;
398
+ }
399
+ /**
400
+ * 构建终端子句 AST (field operator value)
401
+ */
402
+ function buildTerminalClauseAST(node) {
403
+ const children = getChildren(node);
404
+ if (children.length >= 2) {
405
+ const field = extractFieldValue(children[0]);
406
+ const operator = extractOperator(children[1]);
407
+ const value = extractComplexValue(children[1], operator);
408
+ if (field && operator && value) return {
409
+ type: "BinaryExpression",
410
+ operator,
411
+ left: {
412
+ type: "Field",
413
+ value: field
414
+ },
415
+ right: value
416
+ };
417
+ }
418
+ return null;
419
+ }
420
+ /**
421
+ * 构建 NOT 子句 AST
422
+ */
423
+ function buildNotClauseAST(node) {
424
+ const children = getChildren(node);
425
+ for (let i = 0; i < children.length; i++) if (children[i] instanceof TerminalNode && children[i].text.toUpperCase() === LOGICAL_OPERATORS.NOT) {
426
+ const innerExpr = i + 1 < children.length ? buildAST(children[i + 1]) : null;
427
+ return innerExpr ? {
428
+ type: "LogicalExpression",
429
+ operator: LOGICAL_OPERATORS.NOT,
430
+ left: innerExpr,
431
+ right: void 0
432
+ } : null;
433
+ }
434
+ for (let i = 0; i < children.length; i++) {
435
+ const result = buildAST(children[i]);
436
+ if (result) return result;
437
+ }
438
+ return null;
439
+ }
440
+ /**
441
+ * 构建逻辑表达式 AST(OR/AND 通用)
442
+ */
443
+ function buildLogicalClauseAST(node, operator) {
444
+ const children = getChildren(node);
445
+ if (children.length === 1) return buildAST(children[0]);
446
+ if (children.length < 3) return null;
447
+ const opIndices = [];
448
+ for (let i = 0; i < children.length; i++) if (children[i] instanceof TerminalNode && children[i].text.toUpperCase() === operator) opIndices.push(i);
449
+ if (opIndices.length === 0) return buildAST(children[0]);
450
+ const lastOpIndex = opIndices[opIndices.length - 1];
451
+ let result = buildAST(children[lastOpIndex + 1]);
452
+ for (let i = opIndices.length - 1; i >= 0; i--) {
453
+ const leftNode = buildAST(children[i === 0 ? 0 : opIndices[i - 1] + 1]);
454
+ if (leftNode && result) result = {
455
+ type: "LogicalExpression",
456
+ operator,
457
+ left: leftNode,
458
+ right: result
459
+ };
460
+ }
461
+ return result;
462
+ }
463
+ /**
464
+ * 构建 OR 子句 AST
465
+ */
466
+ function buildOrClauseAST(node) {
467
+ return buildLogicalClauseAST(node, LOGICAL_OPERATORS.OR);
468
+ }
469
+ /**
470
+ * 构建 AND 子句 AST
471
+ */
472
+ function buildAndClauseAST(node) {
473
+ return buildLogicalClauseAST(node, LOGICAL_OPERATORS.AND);
474
+ }
475
+ /**
476
+ * 构建列表 AST
477
+ */
478
+ function buildListAST(node) {
479
+ const children = getChildren(node);
480
+ const elements = [];
481
+ for (let i = 0; i < children.length; i++) {
482
+ const child = children[i];
483
+ if (child instanceof TerminalNode && SKIP_TERMINALS.has(child.text)) continue;
484
+ if (getNodeInfo(child).ruleName === JQL_CONTEXT.OPERAND) {
485
+ const operandValue = buildAST(child);
486
+ if (operandValue) elements.push(operandValue);
487
+ }
488
+ }
489
+ return {
490
+ type: "List",
491
+ elements
492
+ };
493
+ }
494
+ /**
495
+ * 构建函数 AST
496
+ */
497
+ function buildFunctionAST(node) {
498
+ const children = getChildren(node);
499
+ let functionName = null;
500
+ const args = [];
501
+ for (let i = 0; i < children.length; i++) {
502
+ const info = getNodeInfo(children[i]);
503
+ if (info.ruleName === JQL_CONTEXT.FUNCTION_NAME) functionName = extractFieldValue(children[i]);
504
+ else if (info.ruleName === JQL_CONTEXT.ARGUMENT_LIST) {
505
+ const argChildren = getChildren(children[i]);
506
+ for (let j = 0; j < argChildren.length; j++) if (getNodeInfo(argChildren[j]).ruleName === JQL_CONTEXT.ARGUMENT) {
507
+ const argValue = extractFieldValue(argChildren[j]);
508
+ if (argValue) args.push({
509
+ type: "Literal",
510
+ value: argValue
511
+ });
512
+ }
513
+ }
514
+ }
515
+ return functionName ? {
516
+ type: "Function",
517
+ name: functionName,
518
+ arguments: args
519
+ } : null;
520
+ }
521
+ /**
522
+ * 构建查询 AST(包含 WHERE 和 ORDER BY)
523
+ */
524
+ function buildQueryAST(node) {
525
+ const children = getChildren(node);
526
+ let whereClause = null;
527
+ let orderByClause = null;
528
+ for (const child of children) {
529
+ const childInfo = getNodeInfo(child);
530
+ if (childInfo.ruleName === JQL_CONTEXT.WHERE) whereClause = buildAST(child);
531
+ else if (childInfo.ruleName === JQL_CONTEXT.ORDER_BY) orderByClause = buildOrderByAST(child);
532
+ }
533
+ if (!orderByClause) return whereClause;
534
+ return {
535
+ type: "JQLQuery",
536
+ where: whereClause,
537
+ orderBy: orderByClause
538
+ };
539
+ }
540
+ /**
541
+ * 构建 ORDER BY 子句的 AST
542
+ */
543
+ function buildOrderByAST(node) {
544
+ if (getNodeInfo(node).ruleName !== JQL_CONTEXT.ORDER_BY) return null;
545
+ const children = getChildren(node);
546
+ const fields = [];
547
+ for (let i = 0; i < children.length; i++) if (getNodeInfo(children[i]).ruleName === JQL_CONTEXT.SEARCH_SORT) {
548
+ const sortChildren = getChildren(children[i]);
549
+ let direction = "ASC";
550
+ let fieldText = "";
551
+ for (let j = 0; j < sortChildren.length; j++) {
552
+ const sortChild = sortChildren[j];
553
+ if (sortChild instanceof TerminalNode) {
554
+ const text = sortChild.text.toUpperCase();
555
+ if (text === "ASC" || text === "DESC") {
556
+ direction = text;
557
+ break;
558
+ }
559
+ fieldText += sortChild.text;
560
+ } else fieldText += sortChild.text;
561
+ }
562
+ const fieldName = fieldText.trim();
563
+ if (fieldName) fields.push({
564
+ type: "OrderByField",
565
+ field: fieldName,
566
+ direction
567
+ });
568
+ }
569
+ return fields.length > 0 ? {
570
+ type: "OrderByClause",
571
+ fields
572
+ } : null;
573
+ }
574
+ /**
575
+ * 提取复杂值(可能是列表、函数或简单值)
576
+ */
577
+ function extractComplexValue(node, operator) {
578
+ if (!node) return null;
579
+ const priorityContexts = [];
580
+ if (operator === NULL_OPERATORS.IS || operator === NULL_OPERATORS.IS_NOT) priorityContexts.push(JQL_CONTEXT.EMPTY);
581
+ else if (operator === SET_OPERATORS.IN || operator === SET_OPERATORS.NOT_IN) priorityContexts.push(JQL_CONTEXT.LIST, JQL_CONTEXT.FUNCTION);
582
+ else priorityContexts.push(JQL_CONTEXT.FUNCTION, JQL_CONTEXT.VALUE);
583
+ for (let i = 0; i < node.childCount; i++) {
584
+ const child = node.getChild(i);
585
+ const childInfo = getNodeInfo(child);
586
+ if (priorityContexts.includes(childInfo.ruleName || "")) {
587
+ const result = buildAST(child);
588
+ if (result) return result;
589
+ }
590
+ }
591
+ for (let i = 0; i < node.childCount; i++) {
592
+ const result = extractComplexValue(node.getChild(i), operator);
593
+ if (result) return result;
594
+ }
595
+ return null;
596
+ }
597
+
598
+ //#endregion
599
+ //#region src/ast-to-iql/field-converters.ts
600
+ /**
601
+ * 规范化字段名,将 cf[xxx] 格式转换为 xxx
602
+ * @param fieldName 原始字段名
603
+ * @returns 规范化后的字段名
604
+ */
605
+ function normalizeFieldName(fieldName) {
606
+ const match = fieldName.match(/^cf\[(\d+)\]$/);
607
+ if (match) return match[1];
608
+ return fieldName;
609
+ }
610
+ /**
611
+ * 过滤 List 中的函数节点(IQL 不支持的函数)
612
+ * @param elements List 中的元素数组
613
+ * @returns 过滤后的元素数组
614
+ */
615
+ function filterFunctions(elements) {
616
+ return elements.filter((el) => el.type !== "Function");
617
+ }
618
+ /**
619
+ * 清理值中的引号包裹
620
+ * @param value 原始值
621
+ * @returns 清理后的值
622
+ */
623
+ function cleanQuotes(value) {
624
+ if (value.startsWith("\"") && value.endsWith("\"") && value.length > 1) return value.slice(1, -1);
625
+ return value;
626
+ }
627
+ /**
628
+ * 通用的字段值转换函数
629
+ * @param value AST 节点
630
+ * @param config 转换配置
631
+ * @returns 转换后的字符串值
632
+ */
633
+ function convertValue(value, config) {
634
+ if (value.type === AST_NODE_TYPES.FUNCTION) {
635
+ if (!config.supportsFunctions) return "";
636
+ const args = value.arguments || [];
637
+ const getArgValue = (arg) => {
638
+ return cleanQuotes(arg.type === AST_NODE_TYPES.LITERAL ? String(arg.value) : String(arg));
639
+ };
640
+ if (value.name === FUNCTION_NAMES.MEMBERS_OF) {
641
+ if (args.length > 0) return `${value.name}(${getArgValue(args[0])})`;
642
+ return `${value.name}()`;
643
+ }
644
+ if (value.name === FUNCTION_NAMES.CASCADE_OPTION && args.length > 0) {
645
+ var _config$valueMap;
646
+ const argValue = getArgValue(args[0]);
647
+ if ((_config$valueMap = config.valueMap) === null || _config$valueMap === void 0 ? void 0 : _config$valueMap[argValue]) return `"${config.valueMap[argValue]}"`;
648
+ return `"${argValue}"`;
649
+ }
650
+ return `"${value.name}()"`;
651
+ }
652
+ if (value.type === AST_NODE_TYPES.LITERAL) {
653
+ var _config$valueMap2;
654
+ let val = cleanQuotes(String(value.value));
655
+ if ((_config$valueMap2 = config.valueMap) === null || _config$valueMap2 === void 0 ? void 0 : _config$valueMap2[val]) {
656
+ const mappedValue = config.valueMap[val];
657
+ val = Array.isArray(mappedValue) ? mappedValue[0] : mappedValue;
658
+ }
659
+ if (config.isNumeric) {
660
+ const numValue = parseFloat(val);
661
+ if (!isNaN(numValue) && numValue < 0) return "";
662
+ return val;
663
+ }
664
+ return `"${val}"`;
665
+ }
666
+ return "";
667
+ }
668
+ /**
669
+ * 通用的等值操作符处理(= 和 !=)
670
+ * @param operator 操作符
671
+ * @param value 值节点
672
+ * @param config 转换配置
673
+ * @returns IQL 查询字符串
674
+ */
675
+ function handleEqualityOperator(operator, value, config) {
676
+ const normalizedOp = operator.toLowerCase();
677
+ if (normalizedOp !== COMPARISON_OPERATORS.EQUAL && normalizedOp !== COMPARISON_OPERATORS.NOT_EQUAL) return "";
678
+ if (value.type === AST_NODE_TYPES.LITERAL) {
679
+ const val = String(value.value);
680
+ if (config.handleEmpty && val.toUpperCase() === NULL_KEYWORDS.EMPTY) {
681
+ const nullOp = normalizedOp === "=" ? "is" : "is not";
682
+ const nullKeyword = config.nullKeyword || "null";
683
+ return `"${config.iqlFieldName}" ${nullOp} ${nullKeyword}`;
684
+ }
685
+ if (config.useInForEquality && config.valueMap) {
686
+ const cleanVal = cleanQuotes(val);
687
+ const mappedValue = config.valueMap[cleanVal];
688
+ const valuesStr = (mappedValue ? Array.isArray(mappedValue) ? mappedValue : [mappedValue] : [cleanVal]).map((v) => `"${v}"`).join(", ");
689
+ const inOp = normalizedOp === "=" ? "in" : "not in";
690
+ return `"${config.iqlFieldName}" ${inOp} [${valuesStr}]`;
691
+ }
692
+ const convertedValue = convertValue(value, config);
693
+ if (convertedValue) return `"${config.iqlFieldName}" ${normalizedOp} ${convertedValue}`;
694
+ }
695
+ if (value.type === AST_NODE_TYPES.FUNCTION && config.supportsFunctions) {
696
+ const convertedValue = convertValue(value, config);
697
+ return `"${config.iqlFieldName}" ${normalizedOp} ${convertedValue}`;
698
+ }
699
+ return "";
700
+ }
701
+ /**
702
+ * 通用的比较操作符处理(>, >=, <, <=)
703
+ * @param operator 操作符
704
+ * @param value 值节点
705
+ * @param config 转换配置
706
+ * @returns IQL 查询字符串
707
+ */
708
+ function handleComparisonOperator(operator, value, config) {
709
+ const normalizedOp = operator.toLowerCase();
710
+ if (![
711
+ COMPARISON_OPERATORS.GREATER_THAN,
712
+ COMPARISON_OPERATORS.GREATER_THAN_OR_EQUAL,
713
+ COMPARISON_OPERATORS.LESS_THAN,
714
+ COMPARISON_OPERATORS.LESS_THAN_OR_EQUAL
715
+ ].includes(normalizedOp)) return "";
716
+ if (value.type === AST_NODE_TYPES.LITERAL) {
717
+ const convertedValue = convertValue(value, config);
718
+ if (convertedValue) return `"${config.iqlFieldName}" ${normalizedOp} ${convertedValue}`;
719
+ }
720
+ return "";
721
+ }
722
+ /**
723
+ * 通用的 IN/NOT IN 操作符处理
724
+ * @param operator 操作符
725
+ * @param value 值节点(应该是 List 类型或 Function 类型)
726
+ * @param config 转换配置
727
+ * @returns IQL 查询字符串
728
+ */
729
+ function handleInOperator(operator, value, config) {
730
+ const normalizedOp = operator.toLowerCase();
731
+ if (normalizedOp !== SET_OPERATORS.IN && normalizedOp !== SET_OPERATORS.NOT_IN) return "";
732
+ if (value.type === AST_NODE_TYPES.FUNCTION && config.supportsFunctions) {
733
+ const convertedValue = convertValue(value, config);
734
+ if (convertedValue) return `"${config.iqlFieldName}" ${normalizedOp} [${convertedValue}]`;
735
+ }
736
+ if (value.type === AST_NODE_TYPES.LIST) {
737
+ const emptyElements = [];
738
+ const nonEmptyElements = [];
739
+ for (const el of value.elements) {
740
+ if (!config.supportsFunctions && el.type === AST_NODE_TYPES.FUNCTION) continue;
741
+ if (config.handleEmpty && el.type === AST_NODE_TYPES.LITERAL && /^empty$/i.test(String(el.value))) emptyElements.push(el);
742
+ else nonEmptyElements.push(el);
743
+ }
744
+ const conditions = [];
745
+ if (nonEmptyElements.length > 0) {
746
+ const allValues = [];
747
+ for (const el of nonEmptyElements) if (el.type === AST_NODE_TYPES.LITERAL) {
748
+ var _config$valueMap3;
749
+ const cleanVal = cleanQuotes(String(el.value));
750
+ const mappedValue = (_config$valueMap3 = config.valueMap) === null || _config$valueMap3 === void 0 ? void 0 : _config$valueMap3[cleanVal];
751
+ if (mappedValue) {
752
+ const values = Array.isArray(mappedValue) ? mappedValue : [mappedValue];
753
+ allValues.push(...values.map((v) => `"${v}"`));
754
+ } else {
755
+ const convertedValue = convertValue(el, config);
756
+ if (convertedValue) allValues.push(convertedValue);
757
+ }
758
+ } else {
759
+ const convertedValue = convertValue(el, config);
760
+ if (convertedValue) allValues.push(convertedValue);
761
+ }
762
+ if (allValues.length > 0) conditions.push(`"${config.iqlFieldName}" ${normalizedOp} [${allValues.join(", ")}]`);
763
+ }
764
+ if (emptyElements.length > 0) {
765
+ const nullOp = normalizedOp === SET_OPERATORS.IN ? NULL_OPERATORS.IS : NULL_OPERATORS.IS_NOT;
766
+ const nullKeyword = config.nullKeyword || NULL_KEYWORDS.NULL;
767
+ conditions.push(`"${config.iqlFieldName}" ${nullOp} ${nullKeyword}`);
768
+ }
769
+ if (conditions.length > 1) {
770
+ const connector = normalizedOp === SET_OPERATORS.IN ? IQL_LOGICAL_OPERATORS.OR : IQL_LOGICAL_OPERATORS.AND;
771
+ return `(${conditions.join(` ${connector} `)})`;
772
+ } else if (conditions.length === 1) return conditions[0];
773
+ }
774
+ return "";
775
+ }
776
+ /**
777
+ * 通用的 IS/IS NOT 操作符处理
778
+ * @param operator 操作符
779
+ * @param value 值节点
780
+ * @param config 转换配置
781
+ * @returns IQL 查询字符串
782
+ */
783
+ function handleIsOperator(operator, value, config) {
784
+ const normalizedOp = operator.toLowerCase();
785
+ if ((normalizedOp === "is" || normalizedOp === "is not") && value.type === "Literal") {
786
+ const val = String(value.value).toUpperCase();
787
+ if (val === "NULL" || val === "EMPTY") {
788
+ const nullKeyword = config.nullKeyword || "null";
789
+ return `"${config.iqlFieldName}" ${normalizedOp} ${nullKeyword}`;
790
+ }
791
+ }
792
+ return "";
793
+ }
794
+ /**
795
+ * 通用的模糊匹配操作符处理(~ 和 !~)
796
+ * @param operator 操作符
797
+ * @param value 值节点
798
+ * @param config 转换配置
799
+ * @returns IQL 查询字符串
800
+ */
801
+ function handleContainsOperator(operator, value, config) {
802
+ const normalizedOp = operator.toLowerCase();
803
+ if ((normalizedOp === "~" || normalizedOp === "!~") && value.type === "Literal") {
804
+ const convertedValue = convertValue(value, config);
805
+ if (convertedValue) return `"${config.iqlFieldName}" ${normalizedOp} ${convertedValue}`;
806
+ }
807
+ return "";
808
+ }
809
+ /**
810
+ * 通用字段转换器
811
+ * @param fieldName 字段名
812
+ * @param operator 操作符
813
+ * @param value 值节点
814
+ * @param constantsMap 常量映射
815
+ * @param options 额外选项
816
+ * @returns IQL 查询字符串
817
+ */
818
+ function convertGenericField(fieldName, operator, value, constantsMap, options = {}) {
819
+ const fieldConfig = (constantsMap.fieldMap || {})[normalizeFieldName(fieldName)];
820
+ const config = {
821
+ iqlFieldName: (fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.customFieldKey) || fieldName,
822
+ supportsFunctions: options.supportsFunctions || false,
823
+ valueMap: options.valueMapKey ? constantsMap[options.valueMapKey] : void 0,
824
+ isNumeric: options.isNumeric || false,
825
+ handleEmpty: options.handleEmpty || false,
826
+ nullKeyword: options.nullKeyword || "null",
827
+ useInForEquality: options.useInForEquality || false
828
+ };
829
+ const normalizedOp = operator.toLowerCase();
830
+ if (normalizedOp === "=" || normalizedOp === "!=") return handleEqualityOperator(operator, value, config);
831
+ if ([
832
+ ">",
833
+ ">=",
834
+ "<",
835
+ "<="
836
+ ].includes(normalizedOp)) return handleComparisonOperator(operator, value, config);
837
+ if (normalizedOp === "in" || normalizedOp === "not in") return handleInOperator(operator, value, config);
838
+ if (normalizedOp === "is" || normalizedOp === "is not") return handleIsOperator(operator, value, config);
839
+ if (normalizedOp === "~" || normalizedOp === "!~") return handleContainsOperator(operator, value, config);
840
+ return "";
841
+ }
842
+ /**
843
+ * 转换 project 字段
844
+ */
845
+ function convertProjectField(operator, value, constantsMap) {
846
+ const projectMap = constantsMap.projectMap || {};
847
+ if (operator === "=") {
848
+ if (value.type === "Literal") {
849
+ const projectKey = String(value.value);
850
+ return `"所属空间" = "${projectMap[projectKey] || projectKey}"`;
851
+ }
852
+ }
853
+ if (operator === "in") {
854
+ if (value.type === "List") return `"所属空间" in [${filterFunctions(value.elements).filter((el) => el.type === "Literal").map((el) => {
855
+ const projectKey = String(el.value);
856
+ return projectMap[projectKey] || projectKey;
857
+ }).map((name) => `"${name}"`).join(", ")}]`;
858
+ }
859
+ return "";
860
+ }
861
+ /**
862
+ * 转换 issuetype 字段
863
+ */
864
+ function convertIssuetypeField(operator, value, constantsMap) {
865
+ if (operator === "=") {
866
+ if (value.type === "Literal") return `"类型" = "${String(value.value)}"`;
867
+ }
868
+ if (operator === "in") {
869
+ if (value.type === "List") return `"类型" in [${filterFunctions(value.elements).filter((el) => el.type === "Literal").map((el) => `"${String(el.value)}"`).join(",")}]`;
870
+ }
871
+ return "";
872
+ }
873
+ /**
874
+ * 转换用户字段
875
+ * @param fieldName JQL 字段名
876
+ * @param operator 操作符
877
+ * @param value 值
878
+ * @param constantsMap 常量映射,包含 fieldMap 配置
879
+ */
880
+ function convertUserField(fieldName, operator, value, constantsMap) {
881
+ return convertGenericField(fieldName, operator, value, constantsMap, {
882
+ supportsFunctions: true,
883
+ handleEmpty: true
884
+ });
885
+ }
886
+ /**
887
+ * 转换版本字段
888
+ * @param fieldName JQL 字段名
889
+ * @param operator 操作符
890
+ * @param value 值
891
+ * @param constantsMap 常量映射
892
+ */
893
+ function convertVersionField(fieldName, operator, value, constantsMap) {
894
+ return convertGenericField(fieldName, operator, value, constantsMap, {
895
+ valueMapKey: "versionMap",
896
+ handleEmpty: true,
897
+ nullKeyword: "NULL",
898
+ useInForEquality: true
899
+ });
900
+ }
901
+ /**
902
+ * 转换 component 字段(Tree 类型)
903
+ * @param operator 操作符
904
+ * @param value 值
905
+ * @param constantsMap 常量映射
906
+ */
907
+ function convertComponentField(operator, value, constantsMap) {
908
+ return convertGenericField("所属模块", operator, value, constantsMap, {
909
+ valueMapKey: "component",
910
+ handleEmpty: true,
911
+ nullKeyword: "NULL"
912
+ });
913
+ }
914
+ /**
915
+ * 转换 Cascade 字段(级联选择)
916
+ * @param fieldName JQL 字段名
917
+ * @param operator 操作符
918
+ * @param value 值
919
+ * @param constantsMap 常量映射
920
+ */
921
+ function convertCascadeField(fieldName, operator, value, constantsMap) {
922
+ return convertGenericField(fieldName, operator, value, constantsMap, {
923
+ supportsFunctions: true,
924
+ valueMapKey: "cascade",
925
+ handleEmpty: true,
926
+ nullKeyword: "null"
927
+ });
928
+ }
929
+ /**
930
+ * 转换状态字段
931
+ * @param operator 操作符
932
+ * @param value 值
933
+ * @param constantsMap 常量映射
934
+ */
935
+ function convertStatusField(operator, value, constantsMap) {
936
+ if (operator === "=") {
937
+ if (value.type === "Literal") return `"状态" = "${String(value.value).replace(/^"|"$/g, "")}"`;
938
+ }
939
+ if (operator === "in") {
940
+ if (value.type === "List") return `"状态" in [${filterFunctions(value.elements).filter((el) => el.type === "Literal").map((el) => {
941
+ return `"${String(el.value).replace(/^"|"$/g, "")}"`;
942
+ }).join(", ")}]`;
943
+ }
944
+ return "";
945
+ }
946
+ /**
947
+ * 转换 Sprint 字段
948
+ * @param operator 操作符
949
+ * @param value 值
950
+ * @param constantsMap 常量映射
951
+ */
952
+ function convertSprintField(operator, value, constantsMap) {
953
+ if (operator === "=") {
954
+ if (value.type === "Literal") return `"迭代" = "${String(value.value)}"`;
955
+ }
956
+ if (operator === "in") {
957
+ if (value.type === "List") return `"迭代" in [${filterFunctions(value.elements).filter((el) => el.type === "Literal").map((el) => {
958
+ return `"${String(el.value)}"`;
959
+ }).join(", ")}]`;
960
+ }
961
+ return "";
962
+ }
963
+ /**
964
+ * 转换 Priority 字段
965
+ * @param operator 操作符
966
+ * @param value 值
967
+ * @param constantsMap 常量映射
968
+ */
969
+ function convertPriorityField(operator, value, constantsMap) {
970
+ return convertGenericField("优先级", operator, value, constantsMap, { handleEmpty: false });
971
+ }
972
+ /**
973
+ * 转换枚举字段(Checkbox/Dropdown/Radio)
974
+ * @param fieldName JQL 字段名
975
+ * @param operator 操作符
976
+ * @param value 值
977
+ * @param constantsMap 常量映射
978
+ */
979
+ function convertEnumField(fieldName, operator, value, constantsMap) {
980
+ const fieldConfig = (constantsMap.fieldMap || {})[normalizeFieldName(fieldName)];
981
+ return convertGenericField(fieldName, operator, value, constantsMap, {
982
+ handleEmpty: false,
983
+ valueMapKey: (fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.customFieldKey) || fieldName
984
+ });
985
+ }
986
+ /**
987
+ * 转换 Text/LongText/Name 字段
988
+ * @param fieldName JQL 字段名
989
+ * @param operator 操作符
990
+ * @param value 值
991
+ * @param constantsMap 常量映射
992
+ */
993
+ function convertTextField(fieldName, operator, value, constantsMap) {
994
+ return convertGenericField(fieldName, operator, value, constantsMap, { handleEmpty: false });
995
+ }
996
+ /**
997
+ * 转换数值字段 (Number/Integer/Float)
998
+ * @param fieldName JQL 字段名
999
+ * @param operator 操作符
1000
+ * @param value 值
1001
+ * @param constantsMap 常量映射
1002
+ */
1003
+ function convertNumberField(fieldName, operator, value, constantsMap) {
1004
+ return convertGenericField(fieldName, operator, value, constantsMap, {
1005
+ isNumeric: true,
1006
+ handleEmpty: true,
1007
+ nullKeyword: "null"
1008
+ });
1009
+ }
1010
+ /**
1011
+ * 转换用户组字段值(使用单引号)
1012
+ * @param value AST 节点
1013
+ * @returns 转换后的字符串值
1014
+ */
1015
+ function convertUserGroupValue(value) {
1016
+ if (value.type === "Literal") return `'${cleanQuotes(String(value.value))}'`;
1017
+ return "";
1018
+ }
1019
+ /**
1020
+ * 转换用户组字段 (UserGroup)
1021
+ * @param fieldName JQL 字段名
1022
+ * @param operator 操作符
1023
+ * @param value 值
1024
+ * @param constantsMap 常量映射
1025
+ */
1026
+ function convertUserGroupField(fieldName, operator, value, constantsMap) {
1027
+ const fieldConfig = (constantsMap.fieldMap || {})[normalizeFieldName(fieldName)];
1028
+ const iqlFieldName = (fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.customFieldKey) || fieldName;
1029
+ const normalizedOp = operator.toLowerCase();
1030
+ if (normalizedOp === "=" || normalizedOp === "!=") {
1031
+ const convertedValue = convertUserGroupValue(value);
1032
+ if (convertedValue) return `"${iqlFieldName}" ${normalizedOp} ${convertedValue}`;
1033
+ return "";
1034
+ }
1035
+ if (normalizedOp === "in" || normalizedOp === "not in") {
1036
+ if (value.type === "List") {
1037
+ const conditions = [];
1038
+ const values = [];
1039
+ let hasEmpty = false;
1040
+ for (const el of value.elements) if (el.type === "Literal" && String(el.value).toUpperCase() === "EMPTY") hasEmpty = true;
1041
+ else {
1042
+ const convertedValue = convertUserGroupValue(el);
1043
+ if (convertedValue) values.push(convertedValue);
1044
+ }
1045
+ if (values.length > 0) conditions.push(`"${iqlFieldName}" ${normalizedOp} [${values.join(", ")}]`);
1046
+ if (hasEmpty) {
1047
+ const nullOp = normalizedOp === "in" ? "is" : "is not";
1048
+ conditions.push(`"${iqlFieldName}" ${nullOp} null`);
1049
+ }
1050
+ if (conditions.length > 1) {
1051
+ const connector = normalizedOp === "in" ? "or" : "and";
1052
+ return `(${conditions.join(` ${connector} `)})`;
1053
+ } else if (conditions.length === 1) return conditions[0];
1054
+ }
1055
+ return "";
1056
+ }
1057
+ if (normalizedOp === "is" || normalizedOp === "is not") {
1058
+ if (value.type === "Literal") {
1059
+ const val = String(value.value).toUpperCase();
1060
+ if (val === "NULL" || val === "EMPTY") return `"${iqlFieldName}" ${normalizedOp} null`;
1061
+ }
1062
+ return "";
1063
+ }
1064
+ return "";
1065
+ }
1066
+ /**
1067
+ * IQL 支持的日期函数列表
1068
+ */
1069
+ const SUPPORTED_DATE_FUNCTIONS = [
1070
+ "startOfDay",
1071
+ "endOfDay",
1072
+ "startOfWeek",
1073
+ "endOfWeek",
1074
+ "startOfMonth",
1075
+ "endOfMonth",
1076
+ "startOfYear",
1077
+ "endOfYear"
1078
+ ];
1079
+ /**
1080
+ * 转换日期函数节点
1081
+ * @param funcNode 函数节点
1082
+ * @returns 转换后的函数字符串,不支持的函数返回空字符串
1083
+ */
1084
+ function convertDateFunction(funcNode) {
1085
+ if (funcNode.type !== "Function") return "";
1086
+ const funcName = funcNode.name;
1087
+ if (!SUPPORTED_DATE_FUNCTIONS.includes(funcName)) return "";
1088
+ const args = funcNode.arguments || [];
1089
+ if (args.length === 0) return `${funcName}()`;
1090
+ else if (args.length === 1) {
1091
+ const arg = args[0];
1092
+ return `${funcName}(${typeof arg === "object" && arg !== null && "value" in arg ? arg.value : arg})`;
1093
+ }
1094
+ return "";
1095
+ }
1096
+ /**
1097
+ * 转换日期字段值
1098
+ * @param value AST 节点
1099
+ * @returns 转换后的字符串值
1100
+ */
1101
+ function convertDateValue(value) {
1102
+ if (value.type === "Function") return convertDateFunction(value);
1103
+ if (value.type === "Literal") {
1104
+ const cleanVal = cleanQuotes(String(value.value));
1105
+ const timeUnitMatch = cleanVal.match(/^([+-]?\d+)([mhdw])$/i);
1106
+ if (timeUnitMatch) {
1107
+ const num = parseInt(timeUnitMatch[1], 10);
1108
+ const unit = timeUnitMatch[2].toLowerCase();
1109
+ if (unit === "m" || unit === "h") return "";
1110
+ if (unit === "d") return num === 0 ? "startOfDay()" : `startOfDay(${num})`;
1111
+ if (unit === "w") {
1112
+ const days = num * 7;
1113
+ return days === 0 ? "startOfDay()" : `startOfDay(${days})`;
1114
+ }
1115
+ }
1116
+ return `"${cleanVal}"`;
1117
+ }
1118
+ return "";
1119
+ }
1120
+ /**
1121
+ * 转换日期字段 (Date/createdAt/updatedAt)
1122
+ * @param fieldName JQL 字段名
1123
+ * @param operator 操作符
1124
+ * @param value 值
1125
+ * @param constantsMap 常量映射
1126
+ */
1127
+ function convertDateField(fieldName, operator, value, constantsMap) {
1128
+ const fieldConfig = (constantsMap.fieldMap || {})[normalizeFieldName(fieldName)];
1129
+ const iqlFieldName = (fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.customFieldKey) || fieldName;
1130
+ const normalizedOp = operator.toLowerCase();
1131
+ if (normalizedOp === "=" || normalizedOp === "!=") {
1132
+ const convertedValue = convertDateValue(value);
1133
+ if (convertedValue) return `"${iqlFieldName}" ${normalizedOp} ${convertedValue}`;
1134
+ return "";
1135
+ }
1136
+ if ([
1137
+ ">",
1138
+ ">=",
1139
+ "<",
1140
+ "<="
1141
+ ].includes(normalizedOp)) {
1142
+ const convertedValue = convertDateValue(value);
1143
+ if (convertedValue) return `"${iqlFieldName}" ${normalizedOp} ${convertedValue}`;
1144
+ return "";
1145
+ }
1146
+ if (normalizedOp === "in" || normalizedOp === "not in") {
1147
+ if (value.type === "List") {
1148
+ let hasEmpty = false;
1149
+ const values = [];
1150
+ for (const el of value.elements) if (el.type === "Literal" && String(el.value).toUpperCase() === "EMPTY") hasEmpty = true;
1151
+ else {
1152
+ const convertedValue = convertDateValue(el);
1153
+ if (convertedValue) values.push(convertedValue);
1154
+ }
1155
+ if (hasEmpty && values.length === 0) return `"${iqlFieldName}" ${normalizedOp === "in" ? "is" : "is not"} null`;
1156
+ if (hasEmpty && values.length > 0) {
1157
+ const nullOp = normalizedOp === "in" ? "is" : "is not";
1158
+ return `("${iqlFieldName}" ${normalizedOp} [${values.join(", ")}] or "${iqlFieldName}" ${nullOp} null)`;
1159
+ }
1160
+ }
1161
+ return "";
1162
+ }
1163
+ if (normalizedOp === "is" || normalizedOp === "is not") {
1164
+ if (value.type === "Literal") {
1165
+ const val = String(value.value).toUpperCase();
1166
+ if (val === "NULL" || val === "EMPTY") return `"${iqlFieldName}" ${normalizedOp} null`;
1167
+ }
1168
+ return "";
1169
+ }
1170
+ return "";
1171
+ }
1172
+
1173
+ //#endregion
1174
+ //#region src/ast-to-iql/iql-converter.ts
1175
+ /**
1176
+ * 验证运算符是否合法
1177
+ * @param operator 运算符
1178
+ * @param rightNode 右侧节点
1179
+ * @returns 是否合法
1180
+ */
1181
+ function isValidOperator(operator, rightNode) {
1182
+ const normalizedOp = operator.toLowerCase();
1183
+ const nodeType = rightNode === null || rightNode === void 0 ? void 0 : rightNode.type;
1184
+ const nodeValue = rightNode === null || rightNode === void 0 ? void 0 : rightNode.value;
1185
+ const isNull = nodeType === "Literal" && /^(null|empty)$/i.test(String(nodeValue));
1186
+ switch (normalizedOp) {
1187
+ case COMPARISON_OPERATORS.EQUAL:
1188
+ case COMPARISON_OPERATORS.NOT_EQUAL:
1189
+ case COMPARISON_OPERATORS.GREATER_THAN:
1190
+ case COMPARISON_OPERATORS.GREATER_THAN_OR_EQUAL:
1191
+ case COMPARISON_OPERATORS.LESS_THAN:
1192
+ case COMPARISON_OPERATORS.LESS_THAN_OR_EQUAL:
1193
+ case COMPARISON_OPERATORS.LIKE:
1194
+ case COMPARISON_OPERATORS.NOT_LIKE: return nodeType === AST_NODE_TYPES.LITERAL && !isNull || nodeType === AST_NODE_TYPES.FUNCTION;
1195
+ case SET_OPERATORS.IN:
1196
+ case SET_OPERATORS.NOT_IN: return nodeType === AST_NODE_TYPES.LIST || nodeType === AST_NODE_TYPES.FUNCTION;
1197
+ case NULL_OPERATORS.IS:
1198
+ case NULL_OPERATORS.IS_NOT: return isNull;
1199
+ default: return false;
1200
+ }
1201
+ }
1202
+ /**
1203
+ * 将 AST 节点转换为 IQL 查询字符串
1204
+ */
1205
+ function astToIQL(ast, constantsMap) {
1206
+ if (ast.type === AST_NODE_TYPES.BINARY_EXPRESSION) return convertBinaryExpression(ast, constantsMap);
1207
+ if (ast.type === AST_NODE_TYPES.LOGICAL_EXPRESSION) return convertLogicalExpression(ast, constantsMap);
1208
+ if (ast.type === AST_NODE_TYPES.JQL_QUERY) {
1209
+ let result = "";
1210
+ if (ast.where) result = astToIQL(ast.where, constantsMap);
1211
+ if (ast.orderBy) {
1212
+ const orderByClause = convertOrderByClause(ast.orderBy, constantsMap);
1213
+ if (orderByClause) result = result ? `${result} ${orderByClause}` : orderByClause;
1214
+ }
1215
+ return result;
1216
+ }
1217
+ return "";
1218
+ }
1219
+ const FIELD_TYPE_GROUPS = {
1220
+ user: new Set([
1221
+ "createdBy",
1222
+ "updatedBy",
1223
+ "Assignee",
1224
+ "Reporter",
1225
+ "User"
1226
+ ]),
1227
+ version: new Set(["Version", "CustomVersion"]),
1228
+ text: new Set([
1229
+ "Text",
1230
+ "LongText",
1231
+ "Name",
1232
+ "Key"
1233
+ ]),
1234
+ number: new Set(["Number", "StoryPoint"]),
1235
+ date: new Set([
1236
+ "Date",
1237
+ "createdAt",
1238
+ "updatedAt"
1239
+ ]),
1240
+ enum: new Set([
1241
+ "Checkbox",
1242
+ "Dropdown",
1243
+ "Radio"
1244
+ ])
1245
+ };
1246
+ const SPECIAL_FIELD_HANDLERS = new Map([
1247
+ ["project", convertProjectField],
1248
+ ["issuetype", convertIssuetypeField],
1249
+ ["status", convertStatusField],
1250
+ ["component", convertComponentField],
1251
+ ["priority", convertPriorityField]
1252
+ ]);
1253
+ /**
1254
+ * 转换二元表达式
1255
+ */
1256
+ function convertBinaryExpression(expr, constantsMap) {
1257
+ var _expr$left, _constantsMap$fieldMa;
1258
+ let field = (_expr$left = expr.left) === null || _expr$left === void 0 ? void 0 : _expr$left.value;
1259
+ const operator = expr.operator;
1260
+ const right = expr.right;
1261
+ if ((field === null || field === void 0 ? void 0 : field.startsWith("\"")) && field.endsWith("\"")) field = field.slice(1, -1);
1262
+ if (!isValidOperator(operator, right)) return "";
1263
+ const handler = SPECIAL_FIELD_HANDLERS.get(field);
1264
+ if (handler) return handler(operator, right, constantsMap);
1265
+ if (field === "labels") return convertTextField("标签", operator, right, constantsMap);
1266
+ if (field === "sprint" || field === "Sprint") return convertSprintField(operator, right, constantsMap);
1267
+ if (field === "text") return convertTextField("text", operator, right, constantsMap);
1268
+ if (field === "created") return convertDateField("created", operator, right, {
1269
+ ...constantsMap,
1270
+ fieldMap: {
1271
+ ...constantsMap.fieldMap,
1272
+ created: {
1273
+ objectId: "system-created",
1274
+ customFieldKey: "创建时间",
1275
+ fieldTypeKey: "createdAt"
1276
+ }
1277
+ }
1278
+ });
1279
+ if (field === "updated") return convertDateField("updated", operator, right, {
1280
+ ...constantsMap,
1281
+ fieldMap: {
1282
+ ...constantsMap.fieldMap,
1283
+ updated: {
1284
+ objectId: "system-updated",
1285
+ customFieldKey: "修改时间",
1286
+ fieldTypeKey: "updatedAt"
1287
+ }
1288
+ }
1289
+ });
1290
+ const normalizedFieldName = normalizeFieldName(field);
1291
+ const fieldConfig = (_constantsMap$fieldMa = constantsMap.fieldMap) === null || _constantsMap$fieldMa === void 0 ? void 0 : _constantsMap$fieldMa[normalizedFieldName];
1292
+ if (!fieldConfig || !(fieldConfig.fieldTypeKey in SUPPORTED_FIELD_TYPES)) return "";
1293
+ const fieldType = fieldConfig.fieldTypeKey;
1294
+ if (FIELD_TYPE_GROUPS.user.has(fieldType)) return convertUserField(field, operator, right, constantsMap);
1295
+ if (FIELD_TYPE_GROUPS.version.has(fieldType)) return convertVersionField(field, operator, right, constantsMap);
1296
+ if (FIELD_TYPE_GROUPS.text.has(fieldType)) return convertTextField(field, operator, right, constantsMap);
1297
+ if (FIELD_TYPE_GROUPS.number.has(fieldType)) return convertNumberField(field, operator, right, constantsMap);
1298
+ if (FIELD_TYPE_GROUPS.date.has(fieldType)) return convertDateField(field, operator, right, constantsMap);
1299
+ if (FIELD_TYPE_GROUPS.enum.has(fieldType)) return convertEnumField(field, operator, right, constantsMap);
1300
+ if (fieldType === "UserGroup") return convertUserGroupField(field, operator, right, constantsMap);
1301
+ if (fieldType === "Cascade") return convertCascadeField(field, operator, right, constantsMap);
1302
+ return "";
1303
+ }
1304
+ /**
1305
+ * 转换 ORDER BY 子句
1306
+ */
1307
+ function convertOrderByClause(orderBy, constantsMap) {
1308
+ if (!orderBy || orderBy.type !== AST_NODE_TYPES.ORDER_BY_CLAUSE || !orderBy.fields || orderBy.fields.length === 0) return "";
1309
+ const fieldMap = constantsMap.fieldMap || {};
1310
+ const orderFields = [];
1311
+ for (const orderField of orderBy.fields) {
1312
+ const fieldName = orderField.field;
1313
+ const direction = orderField.direction.toLowerCase();
1314
+ const fieldConfig = fieldMap[normalizeFieldName(fieldName)];
1315
+ const iqlFieldName = (fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.customFieldKey) || fieldName;
1316
+ orderFields.push(`${iqlFieldName} ${direction}`);
1317
+ }
1318
+ if (orderFields.length === 0) return "";
1319
+ return `${SQL_CLAUSES.ORDER_BY} ${orderFields.join(",")}`;
1320
+ }
1321
+ /**
1322
+ * 转换逻辑表达式
1323
+ */
1324
+ function convertLogicalExpression(expr, constantsMap) {
1325
+ const operator = expr.operator;
1326
+ const left = expr.left ? astToIQL(expr.left, constantsMap) : "";
1327
+ const right = expr.right ? astToIQL(expr.right, constantsMap) : "";
1328
+ if (operator === LOGICAL_OPERATORS.AND) {
1329
+ if (!left || !right) return "";
1330
+ return `${left} ${IQL_LOGICAL_OPERATORS.AND} ${right}`;
1331
+ }
1332
+ if (operator === LOGICAL_OPERATORS.OR) {
1333
+ if (!left || !right) return "";
1334
+ return `${left} ${IQL_LOGICAL_OPERATORS.OR} ${right}`;
1335
+ }
1336
+ if (operator === LOGICAL_OPERATORS.NOT) {
1337
+ if (!left) return "";
1338
+ return `${IQL_LOGICAL_OPERATORS.NOT} ${left}`;
1339
+ }
1340
+ return "";
1341
+ }
1342
+
1343
+ //#endregion
1344
+ //#region src/field-transformer.ts
1345
+ /**
1346
+ * JQL 系统字段到 IQL 字段的映射
1347
+ */
1348
+ const SYSTEM_FIELD_MAPPING = {
1349
+ project: "workspace",
1350
+ creator: "createdBy",
1351
+ created: "createdAt",
1352
+ updated: "updatedAt",
1353
+ assignee: "assignee",
1354
+ reporter: "reporter",
1355
+ status: "status",
1356
+ Sprint: "sprint",
1357
+ priority: "priority",
1358
+ summary: "name",
1359
+ text: "name",
1360
+ key: "key",
1361
+ "Story Points": "StoryPoint",
1362
+ component: "r_module_management_associated_module",
1363
+ labels: "jira_system_labels",
1364
+ description: "jira_system_description",
1365
+ fixVersion: "version",
1366
+ affectedVersion: "jira_system_versions",
1367
+ environment: "jira_system_environment",
1368
+ resolution: "jira_system_resolution"
1369
+ };
1370
+ /**
1371
+ * FieldType ObjectId 到 FieldTypeKey 的映射
1372
+ */
1373
+ const FIELD_TYPE_MAPPING = {
1374
+ "NTXX60ceoD": "Name",
1375
+ "na20LzXTA2": "Text",
1376
+ "9SZARgA5TI": "LongText",
1377
+ "4DFeBwRLk9": "LongText",
1378
+ "xCY9ooDKKY": "Number",
1379
+ "CpJDd7io5a": "StoryPoint",
1380
+ "xCv93s1xbd": "Number",
1381
+ "cFdABEFcts": "Date",
1382
+ "euBEAkKOz0": "Date",
1383
+ "xCv93s1xut": "Date",
1384
+ "5nYmdVook2": "Tag",
1385
+ "pgCBxAIJjG": "Dropdown",
1386
+ "3wIfFg5kx4": "Radio",
1387
+ "iW2i992pSp": "Checkbox",
1388
+ "AG5wBe2Ir0": "User",
1389
+ "1g5POUEzw9": "createdBy",
1390
+ "IKeDQI8CrC": "updatedBy",
1391
+ "f59j6P7DhC": "Assignee",
1392
+ "Idh8D5b4sF5": "Reporter",
1393
+ "yCE1bJNxOU": "Version",
1394
+ "809afb6a52": "Version",
1395
+ "an27WsuEID": "UserGroup",
1396
+ "5EXG80C96P": "Cascade",
1397
+ "YE5NQcNDAn": "Priority",
1398
+ "voZko9nkcn": "Status",
1399
+ "rETqSrEpjZ": "Sprint",
1400
+ "EPBm2adxxk": "StatusType",
1401
+ "pN1urEY45D": "Key",
1402
+ "IJ9nXcXYKB": "Workspace",
1403
+ "HBZegUsQdF": "ItemType",
1404
+ "2pvaQp1S5U": "ItemType",
1405
+ "pCHjdxS3Lk": "Tree",
1406
+ "wO2Ei7NTDc": "Tree",
1407
+ "TZwrcPvByG": "File",
1408
+ "DgBnXFOc2P": "Text",
1409
+ "gJxcU0Vy8o": "Number",
1410
+ "pmtLzwTuwd": "Text",
1411
+ "JbI5WXiaFk": "Text",
1412
+ "jGXxAc32dP": "Text"
1413
+ };
1414
+ /**
1415
+ * 创建枚举值映射
1416
+ * @param customData 自定义数据数组
1417
+ * @param labelKey 标签的键名('label' 或 'title')
1418
+ * @param inverse 是否反转映射方向(true: value->label, false: label->value)
1419
+ */
1420
+ function createValueMap(customData, labelKey = "label", inverse = false) {
1421
+ const map = {};
1422
+ for (const item of customData) {
1423
+ const label = labelKey === "title" ? item.title || item.label : item.label;
1424
+ if (label && item.value) map[inverse ? item.value : label] = inverse ? label : item.value;
1425
+ }
1426
+ return map;
1427
+ }
1428
+ /**
1429
+ * 将映射添加到多个键
1430
+ */
1431
+ function addMapToKeys(enumValueMaps, keys, valueMap) {
1432
+ for (const key of keys) enumValueMaps[key] = valueMap;
1433
+ }
1434
+ /**
1435
+ * 将原始字段数组转换为 ConstantsMap 格式
1436
+ * 这个函数应该在外部调用一次,然后重复使用返回的 ConstantsMap 进行多次转换
1437
+ *
1438
+ * @param fields 原始字段数组
1439
+ * @param projectMap 项目映射(可选)
1440
+ * @param versionMap 版本映射(可选)
1441
+ * @returns ConstantsMap 对象,可直接传给 jql2iql 使用
1442
+ */
1443
+ function transformFieldsToConstantsMap(fields, projectMap, versionMap) {
1444
+ const fieldMap = {};
1445
+ const enumValueMaps = {};
1446
+ let componentMap;
1447
+ const cascadeMaps = {};
1448
+ for (const field of fields) {
1449
+ const { key, name, objectId, fieldType, data } = field;
1450
+ const fieldTypeKey = FIELD_TYPE_MAPPING[fieldType.objectId] || "Text";
1451
+ const jqlFieldKeys = findJQLFieldKeys(key, name);
1452
+ const fieldInfo = {
1453
+ objectId,
1454
+ customFieldKey: name,
1455
+ fieldTypeKey,
1456
+ fieldTypeId: fieldType.objectId
1457
+ };
1458
+ for (const jqlFieldKey of jqlFieldKeys) fieldMap[jqlFieldKey] = fieldInfo;
1459
+ if (key.startsWith("customfield_")) fieldMap[key.replace("customfield_", "")] = fieldInfo;
1460
+ if ((data === null || data === void 0 ? void 0 : data.customData) && data.customData.length > 0) {
1461
+ const isComponent = key === "r_module_management_associated_module" || name === "所属模块";
1462
+ if (fieldTypeKey === "Cascade") {
1463
+ const valueMap = createValueMap(data.customData, "label", true);
1464
+ addMapToKeys(enumValueMaps, [name, ...jqlFieldKeys], valueMap);
1465
+ Object.assign(cascadeMaps, valueMap);
1466
+ } else if (isComponent) componentMap = createValueMap(data.customData, "title");
1467
+ else {
1468
+ const valueMap = createValueMap(data.customData);
1469
+ if (Object.keys(valueMap).length > 0) addMapToKeys(enumValueMaps, [name, ...jqlFieldKeys], valueMap);
1470
+ }
1471
+ }
1472
+ }
1473
+ return {
1474
+ projectMap,
1475
+ versionMap,
1476
+ component: componentMap,
1477
+ cascade: Object.keys(cascadeMaps).length > 0 ? cascadeMaps : void 0,
1478
+ fieldMap,
1479
+ ...enumValueMaps
1480
+ };
1481
+ }
1482
+ /**
1483
+ * 查找字段的 JQL 字段名(可能返回多个,如"标题"同时映射 text 和 summary)
1484
+ * 对于系统字段,返回 JQL 标准名称;对于自定义字段,返回字段名或 key
1485
+ */
1486
+ function findJQLFieldKeys(key, name) {
1487
+ const nameToJQLMap = {
1488
+ "标题": ["text", "summary"],
1489
+ "负责人": ["assignee"],
1490
+ "创建人": ["creator"],
1491
+ "创建时间": ["created"],
1492
+ "更新人": ["updatedBy"],
1493
+ "报告人": ["reporter"],
1494
+ "优先级": ["priority"],
1495
+ "状态": ["status"],
1496
+ "迭代": ["Sprint"],
1497
+ "所属模块": ["component"],
1498
+ "标签": ["labels"],
1499
+ "描述": ["description"]
1500
+ };
1501
+ if (nameToJQLMap[name]) return nameToJQLMap[name];
1502
+ const matchedKeys = [];
1503
+ for (const [jqlKey, iqlKey] of Object.entries(SYSTEM_FIELD_MAPPING)) if (key === iqlKey) matchedKeys.push(jqlKey);
1504
+ if (matchedKeys.length > 0) return matchedKeys;
1505
+ return [name];
1506
+ }
1507
+
1508
+ //#endregion
1509
+ //#region src/index.ts
1510
+ /**
1511
+ * 解析 JQL 查询字符串为 AST
1512
+ */
1513
+ function parseJQL(jqlText) {
1514
+ return buildAST(new JQLParser(new CommonTokenStream(new JQLLexer(CharStreams.fromString(jqlText)))).jqlQuery());
1515
+ }
1516
+ /**
1517
+ * 将 JQL 转换为 IQL
1518
+ */
1519
+ function jql2iql(jqlText, constantsMap) {
1520
+ const ast = parseJQL(jqlText);
1521
+ if (!ast) return "";
1522
+ return astToIQL(ast, constantsMap);
1523
+ }
1524
+
1525
+ //#endregion
1526
+ export { astToIQL, buildAST, getNodeInfo, jql2iql, parseJQL, transformFieldsToConstantsMap, traverseJQLTree };