a-calc 3.0.0-beta.20260201.2 → 3.0.0-beta.20260201.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a-calc",
3
- "version": "3.0.0-beta.20260201.2",
3
+ "version": "3.0.0-beta.20260201.3",
4
4
  "description": "A very powerful and easy-to-use number precision calculation and formatting library.",
5
5
  "main": "./cjs/index.js",
6
6
  "exports": {
@@ -43,12 +43,12 @@ function get_acalc_completions(ts_module, ls, file_name, position, logger, get_c
43
43
  return undefined;
44
44
  const type_checker = program.getTypeChecker();
45
45
  const context = (0, utils_1.is_in_acalc_string)(ts_module, source_file, position, type_checker, logger);
46
- logger.info(`a-calc-ts-plugin: in_string=${context.in_string}, offset=${context.offset_in_string}, has_second_arg=${!!context.second_arg}`);
46
+ logger.info(`a-calc-ts-plugin: in_string=${context.in_string}, offset=${context.offset_in_string}, has_second_arg=${!!context.second_arg}, is_format_only=${context.is_format_only}`);
47
47
  if (!context.in_string)
48
48
  return undefined;
49
49
  const text_before = (0, utils_1.get_text_before_cursor)(context.string_content, context.offset_in_string);
50
50
  logger.info(`a-calc-ts-plugin: text_before="${text_before}"`);
51
- const trigger = (0, utils_1.detect_trigger)(text_before);
51
+ const trigger = (0, utils_1.detect_trigger)(text_before, context.is_format_only);
52
52
  logger.info(`a-calc-ts-plugin: trigger=${JSON.stringify(trigger)}`);
53
53
  // 在 a-calc 字符串内,如果没有特定触发,返回空补全列表
54
54
  // 避免原始 TypeScript 补全显示奇怪的内容
@@ -10,6 +10,8 @@ export interface AcalcStringContext {
10
10
  second_arg?: ts.Expression;
11
11
  /** 函数名 */
12
12
  func_name?: string;
13
+ /** 是否在纯格式化区域(fmt 第二参数、链式调用第二括号) */
14
+ is_format_only?: boolean;
13
15
  }
14
16
  /**
15
17
  * 检测位置是否在 calc/fmt 等函数的字符串参数内
@@ -45,5 +47,7 @@ export type TriggerType = {
45
47
  } | null;
46
48
  /**
47
49
  * 检测补全触发类型
50
+ * @param text_before 光标前的文本
51
+ * @param is_format_only 是否在纯格式化区域(fmt 第二参数、链式调用第二括号)
48
52
  */
49
- export declare function detect_trigger(text_before: string): TriggerType;
53
+ export declare function detect_trigger(text_before: string, is_format_only?: boolean): TriggerType;
@@ -17,6 +17,22 @@ function is_in_acalc_string(ts_module, source_file, position, type_checker, logg
17
17
  if (!is_from_acalc(ts_module, node, type_checker, logger)) {
18
18
  return ts_module.forEachChild(node, visit) || false;
19
19
  }
20
+ // fmt 函数:检查第二个参数是否是格式化字符串
21
+ if (func_name === 'fmt' && node.arguments.length >= 2) {
22
+ const second_arg = node.arguments[1];
23
+ if (is_string_literal(ts_module, second_arg)) {
24
+ const start = second_arg.getStart(source_file) + 1;
25
+ const end = second_arg.getEnd() - 1;
26
+ if (position >= start && position <= end) {
27
+ result.in_string = true;
28
+ result.string_content = get_string_content(ts_module, second_arg);
29
+ result.offset_in_string = position - start;
30
+ result.func_name = func_name;
31
+ result.is_format_only = true;
32
+ return true;
33
+ }
34
+ }
35
+ }
20
36
  // 检查第一个参数是否是字符串
21
37
  const first_arg = node.arguments[0];
22
38
  if (first_arg && is_string_literal(ts_module, first_arg)) {
@@ -35,6 +51,22 @@ function is_in_acalc_string(ts_module, source_file, position, type_checker, logg
35
51
  }
36
52
  }
37
53
  }
54
+ // 链式调用检测: cadd(1, 2)("=2") 或 cadd(1).mul(2)("=2")
55
+ if (is_chain_call_format_arg(ts_module, node, type_checker, logger)) {
56
+ const first_arg = node.arguments[0];
57
+ if (first_arg && is_string_literal(ts_module, first_arg)) {
58
+ const start = first_arg.getStart(source_file) + 1;
59
+ const end = first_arg.getEnd() - 1;
60
+ if (position >= start && position <= end) {
61
+ result.in_string = true;
62
+ result.string_content = get_string_content(ts_module, first_arg);
63
+ result.offset_in_string = position - start;
64
+ result.func_name = 'chain_format';
65
+ result.is_format_only = true;
66
+ return true;
67
+ }
68
+ }
69
+ }
38
70
  }
39
71
  return ts_module.forEachChild(node, visit) || false;
40
72
  }
@@ -42,49 +74,106 @@ function is_in_acalc_string(ts_module, source_file, position, type_checker, logg
42
74
  return result;
43
75
  }
44
76
  exports.is_in_acalc_string = is_in_acalc_string;
77
+ /**
78
+ * 检测是否是链式调用的格式化参数
79
+ * cadd(1, 2).mul(3)("=2") 中最后的括号
80
+ */
81
+ function is_chain_call_format_arg(ts_module, node, type_checker, logger) {
82
+ if (!type_checker)
83
+ return false;
84
+ try {
85
+ // 获取被调用表达式的类型
86
+ const expr = node.expression;
87
+ const expr_type = type_checker.getTypeAtLocation(expr);
88
+ const type_str = type_checker.typeToString(expr_type);
89
+ logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: chain check - expr type: ${type_str}`);
90
+ // 检查类型是否是 ChainValue
91
+ if (type_str === 'ChainValue') {
92
+ return true;
93
+ }
94
+ // 也检查类型的调用签名
95
+ const call_signatures = expr_type.getCallSignatures();
96
+ for (const sig of call_signatures) {
97
+ const return_type = type_checker.typeToString(sig.getReturnType());
98
+ logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: chain check - call sig return: ${return_type}`);
99
+ // ChainValue 调用后返回 string
100
+ if (return_type === 'string') {
101
+ // 检查参数是否是可选的 format 字符串
102
+ const params = sig.getParameters();
103
+ if (params.length <= 1) {
104
+ // 进一步验证:检查表达式是否来自链式调用
105
+ if (is_chain_expression(ts_module, expr, type_checker, logger)) {
106
+ return true;
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ catch (e) {
113
+ logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: chain check error: ${e}`);
114
+ }
115
+ return false;
116
+ }
117
+ /**
118
+ * 检测表达式是否是链式调用表达式
119
+ */
120
+ function is_chain_expression(ts_module, expr, type_checker, logger) {
121
+ // 链式方法调用: cadd(1).sub(2)
122
+ if (ts_module.isCallExpression(expr)) {
123
+ const call_expr = expr.expression;
124
+ // 检查是否是属性访问 (如 .sub, .mul)
125
+ if (ts_module.isPropertyAccessExpression(call_expr)) {
126
+ const method_name = call_expr.name.text;
127
+ const chain_methods = ['add', 'sub', 'mul', 'div', 'mod', 'pow', 'idiv'];
128
+ if (chain_methods.includes(method_name)) {
129
+ return true;
130
+ }
131
+ }
132
+ // 检查是否是链式起始函数 (cadd, csub 等)
133
+ if (ts_module.isIdentifier(call_expr)) {
134
+ const func_name = call_expr.text;
135
+ const chain_starters = ['cadd', 'csub', 'cmul', 'cdiv', 'cmod', 'cpow', 'cidiv'];
136
+ if (chain_starters.includes(func_name)) {
137
+ return true;
138
+ }
139
+ }
140
+ }
141
+ return false;
142
+ }
45
143
  /**
46
144
  * 检查函数是否来自 a-calc 库
145
+ * 通过检查函数的类型签名来判断
47
146
  */
48
147
  function is_from_acalc(ts_module, node, type_checker, logger) {
49
- var _a, _b;
50
148
  if (!type_checker)
51
149
  return true;
52
150
  try {
53
151
  const expr = node.expression;
54
- const symbol = type_checker.getSymbolAtLocation(expr);
55
- if (!symbol) {
56
- logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - no symbol found');
57
- return false;
58
- }
59
- // 获取声明
60
- const declarations = symbol.getDeclarations();
61
- if (!declarations || declarations.length === 0) {
62
- logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - no declarations found');
63
- return false;
152
+ const type = type_checker.getTypeAtLocation(expr);
153
+ const type_str = type_checker.typeToString(type);
154
+ logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: is_from_acalc - type: ${type_str}`);
155
+ // 检查是否匹配 a-calc 的函数类型签名
156
+ // calc: Calc 类型
157
+ // fmt: Fmt 类型
158
+ // cadd/csub 等: 返回 ChainValue
159
+ if (type_str === 'Calc' || type_str === 'Fmt') {
160
+ logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - matched Calc/Fmt type');
161
+ return true;
64
162
  }
65
- for (const decl of declarations) {
66
- const source_file = decl.getSourceFile();
67
- const file_name = source_file.fileName.replace(/\\/g, '/');
68
- logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: is_from_acalc - checking file: ${file_name}`);
69
- // 匹配 node_modules 中的 a-calc
70
- if (file_name.includes('node_modules/a-calc/')) {
71
- logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - matched node_modules/a-calc');
163
+ // 检查调用签名的返回类型
164
+ const signatures = type.getCallSignatures();
165
+ for (const sig of signatures) {
166
+ const return_type = type_checker.typeToString(sig.getReturnType());
167
+ logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: is_from_acalc - return type: ${return_type}`);
168
+ // ChainValue 是链式调用的返回类型
169
+ if (return_type === 'ChainValue') {
170
+ logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - matched ChainValue return');
72
171
  return true;
73
172
  }
74
- // 检查是否是 ImportSpecifier(从模块导入的)
75
- if (ts_module.isImportSpecifier(decl)) {
76
- const import_decl = (_b = (_a = decl.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.parent;
77
- if (import_decl && ts_module.isImportDeclaration(import_decl)) {
78
- const module_specifier = import_decl.moduleSpecifier;
79
- if (ts_module.isStringLiteral(module_specifier)) {
80
- const module_name = module_specifier.text;
81
- logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: is_from_acalc - import from: ${module_name}`);
82
- if (module_name === 'a-calc') {
83
- logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - matched import from a-calc');
84
- return true;
85
- }
86
- }
87
- }
173
+ // CalcResult 是 calc 的返回类型
174
+ if (return_type.includes('CalcResult') || return_type.includes('& string')) {
175
+ logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - matched CalcResult return');
176
+ return true;
88
177
  }
89
178
  }
90
179
  logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - no match found');
@@ -138,8 +227,10 @@ function get_text_before_cursor(content, offset) {
138
227
  exports.get_text_before_cursor = get_text_before_cursor;
139
228
  /**
140
229
  * 检测补全触发类型
230
+ * @param text_before 光标前的文本
231
+ * @param is_format_only 是否在纯格式化区域(fmt 第二参数、链式调用第二括号)
141
232
  */
142
- function detect_trigger(text_before) {
233
+ function detect_trigger(text_before, is_format_only) {
143
234
  // 检测 !xxx: 后的预设值补全 (如 !t:ind, !c:c)
144
235
  const value_match = text_before.match(/!(t|c|i|g|u|ua|ub|um|uh):([\w]*)$/);
145
236
  if (value_match) {
@@ -150,11 +241,16 @@ function detect_trigger(text_before) {
150
241
  if (name_match) {
151
242
  return { type: 'preset_name', prefix: name_match[1] };
152
243
  }
153
- // 检测快捷语法 : 后的补全 (需要前面是空格或 |)
244
+ // 检测快捷语法 : 后的补全 (需要前面是空格或 | 或字符串开头)
154
245
  const shortcut_match = text_before.match(/(?:^|[\s|]):([\w]*)$/);
155
246
  if (shortcut_match) {
156
247
  return { type: 'shortcut', prefix: shortcut_match[1] };
157
248
  }
249
+ // 纯格式化区域(fmt 第二参数、链式调用第二括号)
250
+ // 整个字符串都是格式化语法,不需要 | 分隔符
251
+ if (is_format_only) {
252
+ return { type: 'format_syntax' };
253
+ }
158
254
  // 检测是否在格式化区域 (| 后面)
159
255
  if (is_in_format_area(text_before)) {
160
256
  // 格式化区域内的变量补全