a-calc 3.0.0-beta.20260130.2 → 3.0.0-beta.20260201.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/a-calc.versions.js +4 -3
- package/browser/index.js +2 -4
- package/cjs/index.js +2 -4
- package/es/index.js +2 -4
- package/mcp-server/src/index.js +0 -0
- package/package.json +130 -128
- package/ts-plugin/dist/config_extractor.d.ts +9 -0
- package/ts-plugin/dist/config_extractor.js +104 -0
- package/ts-plugin/dist/constants.d.ts +34 -0
- package/ts-plugin/dist/constants.js +274 -0
- package/ts-plugin/dist/index.d.ts +7 -0
- package/ts-plugin/dist/index.js +294 -0
- package/ts-plugin/dist/utils.d.ts +49 -0
- package/ts-plugin/dist/utils.js +204 -0
- package/ts-plugin/dist/variable_extractor.d.ts +16 -0
- package/ts-plugin/dist/variable_extractor.js +138 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detect_trigger = exports.get_text_before_cursor = exports.is_in_acalc_string = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 检测位置是否在 calc/fmt 等函数的字符串参数内
|
|
6
|
+
*/
|
|
7
|
+
function is_in_acalc_string(ts_module, source_file, position, type_checker, logger) {
|
|
8
|
+
const result = { in_string: false, string_content: '', offset_in_string: 0 };
|
|
9
|
+
// 目标函数名
|
|
10
|
+
const target_functions = ['calc', 'fmt', 'calc_sum', 'calc_wrap', 'calc_space', 'calc_avg', 'calc_max', 'calc_min', 'calc_count'];
|
|
11
|
+
function visit(node) {
|
|
12
|
+
// 检查是否是函数调用
|
|
13
|
+
if (ts_module.isCallExpression(node)) {
|
|
14
|
+
const func_name = get_function_name(ts_module, node);
|
|
15
|
+
if (func_name && target_functions.includes(func_name)) {
|
|
16
|
+
// 验证函数是否来自 a-calc 库
|
|
17
|
+
if (!is_from_acalc(ts_module, node, type_checker, logger)) {
|
|
18
|
+
return ts_module.forEachChild(node, visit) || false;
|
|
19
|
+
}
|
|
20
|
+
// 检查第一个参数是否是字符串
|
|
21
|
+
const first_arg = node.arguments[0];
|
|
22
|
+
if (first_arg && is_string_literal(ts_module, first_arg)) {
|
|
23
|
+
const start = first_arg.getStart(source_file) + 1; // 跳过引号
|
|
24
|
+
const end = first_arg.getEnd() - 1; // 跳过引号
|
|
25
|
+
if (position >= start && position <= end) {
|
|
26
|
+
result.in_string = true;
|
|
27
|
+
result.string_content = get_string_content(ts_module, first_arg);
|
|
28
|
+
result.offset_in_string = position - start;
|
|
29
|
+
result.func_name = func_name;
|
|
30
|
+
// 保存第二个参数
|
|
31
|
+
if (node.arguments.length > 1) {
|
|
32
|
+
result.second_arg = node.arguments[1];
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return ts_module.forEachChild(node, visit) || false;
|
|
40
|
+
}
|
|
41
|
+
visit(source_file);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
exports.is_in_acalc_string = is_in_acalc_string;
|
|
45
|
+
/**
|
|
46
|
+
* 检查函数是否来自 a-calc 库
|
|
47
|
+
*/
|
|
48
|
+
function is_from_acalc(ts_module, node, type_checker, logger) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
if (!type_checker)
|
|
51
|
+
return true;
|
|
52
|
+
try {
|
|
53
|
+
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;
|
|
64
|
+
}
|
|
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');
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
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
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
logger === null || logger === void 0 ? void 0 : logger.info('a-calc-ts-plugin: is_from_acalc - no match found');
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
logger === null || logger === void 0 ? void 0 : logger.info(`a-calc-ts-plugin: is_from_acalc - error: ${e}`);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 获取函数调用的函数名
|
|
100
|
+
*/
|
|
101
|
+
function get_function_name(ts_module, node) {
|
|
102
|
+
const expr = node.expression;
|
|
103
|
+
// 直接调用: calc(...)
|
|
104
|
+
if (ts_module.isIdentifier(expr)) {
|
|
105
|
+
return expr.text;
|
|
106
|
+
}
|
|
107
|
+
// 属性访问: obj.calc(...)
|
|
108
|
+
if (ts_module.isPropertyAccessExpression(expr)) {
|
|
109
|
+
return expr.name.text;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 检查节点是否是字符串字面量
|
|
115
|
+
*/
|
|
116
|
+
function is_string_literal(ts_module, node) {
|
|
117
|
+
return ts_module.isStringLiteral(node) ||
|
|
118
|
+
ts_module.isNoSubstitutionTemplateLiteral(node);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 获取字符串内容
|
|
122
|
+
*/
|
|
123
|
+
function get_string_content(ts_module, node) {
|
|
124
|
+
if (ts_module.isStringLiteral(node)) {
|
|
125
|
+
return node.text;
|
|
126
|
+
}
|
|
127
|
+
if (ts_module.isNoSubstitutionTemplateLiteral(node)) {
|
|
128
|
+
return node.text;
|
|
129
|
+
}
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 获取光标前的文本,用于判断触发条件
|
|
134
|
+
*/
|
|
135
|
+
function get_text_before_cursor(content, offset) {
|
|
136
|
+
return content.slice(0, offset);
|
|
137
|
+
}
|
|
138
|
+
exports.get_text_before_cursor = get_text_before_cursor;
|
|
139
|
+
/**
|
|
140
|
+
* 检测补全触发类型
|
|
141
|
+
*/
|
|
142
|
+
function detect_trigger(text_before) {
|
|
143
|
+
// 检测 !xxx: 后的预设值补全 (如 !t:ind, !c:c)
|
|
144
|
+
const value_match = text_before.match(/!(t|c|i|g|u|ua|ub|um|uh):([\w]*)$/);
|
|
145
|
+
if (value_match) {
|
|
146
|
+
return { type: 'preset_value', preset: value_match[1], prefix: value_match[2] };
|
|
147
|
+
}
|
|
148
|
+
// 检测 ! 后的预设名补全 (如 !t, !c)
|
|
149
|
+
const name_match = text_before.match(/!([\w]*)$/);
|
|
150
|
+
if (name_match) {
|
|
151
|
+
return { type: 'preset_name', prefix: name_match[1] };
|
|
152
|
+
}
|
|
153
|
+
// 检测快捷语法 : 后的补全 (需要前面是空格或 |)
|
|
154
|
+
const shortcut_match = text_before.match(/(?:^|[\s|]):([\w]*)$/);
|
|
155
|
+
if (shortcut_match) {
|
|
156
|
+
return { type: 'shortcut', prefix: shortcut_match[1] };
|
|
157
|
+
}
|
|
158
|
+
// 检测是否在格式化区域 (| 后面)
|
|
159
|
+
if (is_in_format_area(text_before)) {
|
|
160
|
+
// 格式化区域内的变量补全
|
|
161
|
+
// 检测 @ 后的变量补全(用于单位、预设等)
|
|
162
|
+
const at_var_match = text_before.match(/@([\w.]*)$/);
|
|
163
|
+
if (at_var_match) {
|
|
164
|
+
return { type: 'format_variable', prefix: at_var_match[1] };
|
|
165
|
+
}
|
|
166
|
+
// 检测 = 后的变量补全(用于小数位数)
|
|
167
|
+
const eq_var_match = text_before.match(/=([\w.]*)$/);
|
|
168
|
+
if (eq_var_match) {
|
|
169
|
+
const prefix = eq_var_match[1];
|
|
170
|
+
// 空前缀或以字母开头时触发变量补全,纯数字是精度值不触发
|
|
171
|
+
if (prefix === '' || /^[a-zA-Z]/.test(prefix)) {
|
|
172
|
+
return { type: 'format_variable', prefix };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 检测 > 或 < 后的变量补全(用于范围限制)
|
|
176
|
+
const range_var_match = text_before.match(/[><]([\w.]*)$/);
|
|
177
|
+
if (range_var_match) {
|
|
178
|
+
const prefix = range_var_match[1];
|
|
179
|
+
if (prefix === '' || /^[a-zA-Z]/.test(prefix)) {
|
|
180
|
+
return { type: 'format_variable', prefix };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// 其他情况显示格式化语法
|
|
184
|
+
return { type: 'format_syntax' };
|
|
185
|
+
}
|
|
186
|
+
// 表达式区域:检测变量补全
|
|
187
|
+
// 匹配变量名前缀,需要前面是分隔符(空格、运算符、括号等)或字符串开头
|
|
188
|
+
const var_match = text_before.match(/(?:^|[\s+\-*/%(),:])([a-zA-Z_][\w.]*)$/);
|
|
189
|
+
if (var_match) {
|
|
190
|
+
return { type: 'variable', prefix: var_match[1] };
|
|
191
|
+
}
|
|
192
|
+
// 检测刚开始输入变量名(光标前是分隔符)
|
|
193
|
+
if (/(?:^|[\s+\-*/%(),:])$/.test(text_before)) {
|
|
194
|
+
return { type: 'variable', prefix: '' };
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
exports.detect_trigger = detect_trigger;
|
|
199
|
+
/**
|
|
200
|
+
* 检测是否在格式化区域
|
|
201
|
+
*/
|
|
202
|
+
function is_in_format_area(text_before) {
|
|
203
|
+
return text_before.includes('|');
|
|
204
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type * as ts from 'typescript/lib/tsserverlibrary';
|
|
2
|
+
/**
|
|
3
|
+
* 变量信息
|
|
4
|
+
*/
|
|
5
|
+
export interface VariableInfo {
|
|
6
|
+
/** 变量路径,如 "user.name" */
|
|
7
|
+
path: string;
|
|
8
|
+
/** 变量类型描述 */
|
|
9
|
+
type_desc: string;
|
|
10
|
+
/** 是否有子属性(用于深度补全) */
|
|
11
|
+
has_children: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 从第二个参数中提取所有可用变量
|
|
15
|
+
*/
|
|
16
|
+
export declare function extract_variables_from_arg(ts_module: typeof ts, arg: ts.Expression, type_checker: ts.TypeChecker, logger: ts.server.Logger): VariableInfo[];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extract_variables_from_arg = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 从第二个参数中提取所有可用变量
|
|
6
|
+
*/
|
|
7
|
+
function extract_variables_from_arg(ts_module, arg, type_checker, logger) {
|
|
8
|
+
const result = [];
|
|
9
|
+
const type = type_checker.getTypeAtLocation(arg);
|
|
10
|
+
logger.info(`a-calc-ts-plugin: extracting variables from arg, type=${type_checker.typeToString(type)}`);
|
|
11
|
+
// 检查是否是数组类型
|
|
12
|
+
if (is_array_type(ts_module, type, type_checker)) {
|
|
13
|
+
// 数组类型:提取元素类型的属性
|
|
14
|
+
const element_type = get_array_element_type(ts_module, type, type_checker);
|
|
15
|
+
if (element_type) {
|
|
16
|
+
extract_properties_from_type(ts_module, element_type, '', type_checker, result, logger, 0);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// 对象类型:直接提取属性
|
|
21
|
+
extract_properties_from_type(ts_module, type, '', type_checker, result, logger, 0);
|
|
22
|
+
}
|
|
23
|
+
logger.info(`a-calc-ts-plugin: extracted ${result.length} variables`);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
exports.extract_variables_from_arg = extract_variables_from_arg;
|
|
27
|
+
/**
|
|
28
|
+
* 检查是否是数组类型
|
|
29
|
+
*/
|
|
30
|
+
function is_array_type(ts_module, type, type_checker) {
|
|
31
|
+
const type_str = type_checker.typeToString(type);
|
|
32
|
+
// 检查是否是数组字面量或 Array<T>
|
|
33
|
+
if (type_str.endsWith('[]') || type_str.startsWith('Array<')) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
// 检查 symbol
|
|
37
|
+
const symbol = type.getSymbol();
|
|
38
|
+
if (symbol && symbol.getName() === 'Array') {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// 检查是否有 length 属性和数字索引签名
|
|
42
|
+
if (type_checker.getIndexTypeOfType(type, ts_module.IndexKind.Number)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 获取数组元素类型
|
|
49
|
+
*/
|
|
50
|
+
function get_array_element_type(ts_module, type, type_checker) {
|
|
51
|
+
// 尝试获取数字索引类型
|
|
52
|
+
const index_type = type_checker.getIndexTypeOfType(type, ts_module.IndexKind.Number);
|
|
53
|
+
if (index_type) {
|
|
54
|
+
return index_type;
|
|
55
|
+
}
|
|
56
|
+
// 尝试从类型参数获取
|
|
57
|
+
const type_args = type.typeArguments;
|
|
58
|
+
if (type_args && type_args.length > 0) {
|
|
59
|
+
return type_args[0];
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 从类型中递归提取属性
|
|
65
|
+
*/
|
|
66
|
+
function extract_properties_from_type(ts_module, type, prefix, type_checker, result, logger, depth) {
|
|
67
|
+
// 限制递归深度,防止无限递归
|
|
68
|
+
const MAX_DEPTH = 5;
|
|
69
|
+
if (depth > MAX_DEPTH) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const properties = type.getProperties();
|
|
73
|
+
for (const prop of properties) {
|
|
74
|
+
const prop_name = prop.getName();
|
|
75
|
+
// 跳过以 _ 开头的内部属性(如 _error, _fmt 等配置项)
|
|
76
|
+
if (prop_name.startsWith('_')) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// 跳过一些常见的内置属性
|
|
80
|
+
if (['__proto__', 'constructor', 'prototype'].includes(prop_name)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const full_path = prefix ? `${prefix}.${prop_name}` : prop_name;
|
|
84
|
+
// 获取属性类型
|
|
85
|
+
let prop_type;
|
|
86
|
+
try {
|
|
87
|
+
if (prop.valueDeclaration) {
|
|
88
|
+
prop_type = type_checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// 尝试从声明中获取
|
|
92
|
+
const declarations = prop.getDeclarations();
|
|
93
|
+
if (declarations && declarations.length > 0) {
|
|
94
|
+
prop_type = type_checker.getTypeOfSymbolAtLocation(prop, declarations[0]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
// 忽略获取类型失败的情况
|
|
100
|
+
}
|
|
101
|
+
const type_desc = prop_type ? type_checker.typeToString(prop_type) : 'unknown';
|
|
102
|
+
const has_children = prop_type ? has_object_properties(ts_module, prop_type, type_checker) : false;
|
|
103
|
+
result.push({
|
|
104
|
+
path: full_path,
|
|
105
|
+
type_desc,
|
|
106
|
+
has_children,
|
|
107
|
+
});
|
|
108
|
+
// 如果是对象类型,递归提取子属性
|
|
109
|
+
if (prop_type && has_children) {
|
|
110
|
+
extract_properties_from_type(ts_module, prop_type, full_path, type_checker, result, logger, depth + 1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 检查类型是否有对象属性(用于判断是否需要深度补全)
|
|
116
|
+
*/
|
|
117
|
+
function has_object_properties(ts_module, type, type_checker) {
|
|
118
|
+
// 排除基本类型
|
|
119
|
+
const type_str = type_checker.typeToString(type);
|
|
120
|
+
const primitive_types = ['string', 'number', 'boolean', 'null', 'undefined', 'void', 'never', 'any', 'unknown'];
|
|
121
|
+
if (primitive_types.includes(type_str)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
// 排除数组类型(不递归进入数组元素)
|
|
125
|
+
if (type_str.endsWith('[]') || type_str.startsWith('Array<')) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// 排除函数类型
|
|
129
|
+
const call_signatures = type.getCallSignatures();
|
|
130
|
+
if (call_signatures && call_signatures.length > 0) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
// 检查是否有属性
|
|
134
|
+
const properties = type.getProperties();
|
|
135
|
+
// 过滤掉以 _ 开头的属性
|
|
136
|
+
const user_properties = properties.filter(p => !p.getName().startsWith('_'));
|
|
137
|
+
return user_properties.length > 0;
|
|
138
|
+
}
|