a-calc 3.0.0-beta.20260201.1 → 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 +3 -2
- package/ts-plugin/dist/index.js +2 -2
- package/ts-plugin/dist/utils.d.ts +5 -1
- package/ts-plugin/dist/utils.js +130 -34
- package/ts-plugin/package.json +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a-calc",
|
|
3
|
-
"version": "3.0.0-beta.20260201.
|
|
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": {
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"mcp-server/src",
|
|
54
54
|
"mcp-server/package.json",
|
|
55
55
|
"mcp-server/README.md",
|
|
56
|
-
"ts-plugin/dist"
|
|
56
|
+
"ts-plugin/dist",
|
|
57
|
+
"ts-plugin/package.json"
|
|
57
58
|
],
|
|
58
59
|
"repository": {
|
|
59
60
|
"type": "git",
|
package/ts-plugin/dist/index.js
CHANGED
|
@@ -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;
|
package/ts-plugin/dist/utils.js
CHANGED
|
@@ -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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc -
|
|
63
|
-
return
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
//
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
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
|
// 格式化区域内的变量补全
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "a-calc-ts-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript language service plugin for a-calc expression autocomplete",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"watch": "tsc -w",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"typescript",
|
|
14
|
+
"plugin",
|
|
15
|
+
"a-calc",
|
|
16
|
+
"autocomplete"
|
|
17
|
+
],
|
|
18
|
+
"author": "Autumn",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"typescript": ">=4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
]
|
|
29
|
+
}
|