@boperators/plugin-ts-language-server 0.2.1 → 0.3.0
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/helpers.js +250 -0
- package/dist/index.js +32 -335
- package/package.json +3 -2
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findOverloadEdits = findOverloadEdits;
|
|
4
|
+
exports.simplifyTypeName = simplifyTypeName;
|
|
5
|
+
exports.getOverloadHoverInfo = getOverloadHoverInfo;
|
|
6
|
+
const boperators_1 = require("boperators");
|
|
7
|
+
// ----- Internal helpers -----
|
|
8
|
+
/**
|
|
9
|
+
* Recursively resolve the effective type of an expression, accounting for
|
|
10
|
+
* operator overloads. For sub-expressions that match a registered overload,
|
|
11
|
+
* uses the overload's declared return type instead of what TypeScript infers
|
|
12
|
+
* (since TS doesn't know about operator overloading).
|
|
13
|
+
*/
|
|
14
|
+
function resolveOverloadedType(node, overloadStore) {
|
|
15
|
+
if (boperators_1.Node.isParenthesizedExpression(node)) {
|
|
16
|
+
return resolveOverloadedType(node.getExpression(), overloadStore);
|
|
17
|
+
}
|
|
18
|
+
if (boperators_1.Node.isBinaryExpression(node)) {
|
|
19
|
+
const operatorKind = node.getOperatorToken().getKind();
|
|
20
|
+
if ((0, boperators_1.isOperatorSyntaxKind)(operatorKind)) {
|
|
21
|
+
const leftType = resolveOverloadedType(node.getLeft(), overloadStore);
|
|
22
|
+
const rightType = resolveOverloadedType(node.getRight(), overloadStore);
|
|
23
|
+
const overload = overloadStore.findOverload(operatorKind, leftType, rightType);
|
|
24
|
+
if (overload)
|
|
25
|
+
return overload.returnType;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (boperators_1.Node.isPrefixUnaryExpression(node)) {
|
|
29
|
+
const operatorKind = node.getOperatorToken();
|
|
30
|
+
if ((0, boperators_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind)) {
|
|
31
|
+
const operandType = resolveOverloadedType(node.getOperand(), overloadStore);
|
|
32
|
+
const overload = overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
|
|
33
|
+
if (overload)
|
|
34
|
+
return overload.returnType;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (boperators_1.Node.isPostfixUnaryExpression(node)) {
|
|
38
|
+
const operatorKind = node.getOperatorToken();
|
|
39
|
+
if ((0, boperators_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind)) {
|
|
40
|
+
const operandType = resolveOverloadedType(node.getOperand(), overloadStore);
|
|
41
|
+
const overload = overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
|
|
42
|
+
if (overload)
|
|
43
|
+
return overload.returnType;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return (0, boperators_1.resolveExpressionType)(node);
|
|
47
|
+
}
|
|
48
|
+
// ----- Exported helpers -----
|
|
49
|
+
/**
|
|
50
|
+
* Before transformation, find all expressions (binary, prefix unary, postfix unary)
|
|
51
|
+
* that match registered overloads and record their operator token positions.
|
|
52
|
+
* This is used to provide hover info for overloaded operators.
|
|
53
|
+
*/
|
|
54
|
+
function findOverloadEdits(sourceFile, overloadStore) {
|
|
55
|
+
const edits = [];
|
|
56
|
+
const binaryExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.BinaryExpression);
|
|
57
|
+
for (const expression of binaryExpressions) {
|
|
58
|
+
const operatorToken = expression.getOperatorToken();
|
|
59
|
+
const operatorKind = operatorToken.getKind();
|
|
60
|
+
if (!(0, boperators_1.isOperatorSyntaxKind)(operatorKind))
|
|
61
|
+
continue;
|
|
62
|
+
const leftType = resolveOverloadedType(expression.getLeft(), overloadStore);
|
|
63
|
+
const rightType = resolveOverloadedType(expression.getRight(), overloadStore);
|
|
64
|
+
const overloadDesc = overloadStore.findOverload(operatorKind, leftType, rightType);
|
|
65
|
+
if (!overloadDesc)
|
|
66
|
+
continue;
|
|
67
|
+
edits.push({
|
|
68
|
+
operatorStart: operatorToken.getStart(),
|
|
69
|
+
operatorEnd: operatorToken.getEnd(),
|
|
70
|
+
hoverStart: expression.getLeft().getEnd(),
|
|
71
|
+
hoverEnd: expression.getRight().getStart(),
|
|
72
|
+
exprStart: expression.getStart(),
|
|
73
|
+
exprEnd: expression.getEnd(),
|
|
74
|
+
className: overloadDesc.className,
|
|
75
|
+
classFilePath: overloadDesc.classFilePath,
|
|
76
|
+
operatorString: overloadDesc.operatorString,
|
|
77
|
+
returnType: overloadDesc.returnType,
|
|
78
|
+
lhsType: leftType,
|
|
79
|
+
rhsType: rightType,
|
|
80
|
+
isStatic: overloadDesc.isStatic,
|
|
81
|
+
kind: "binary",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Scan prefix unary expressions
|
|
85
|
+
const prefixExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.PrefixUnaryExpression);
|
|
86
|
+
for (const expression of prefixExpressions) {
|
|
87
|
+
const operatorKind = expression.getOperatorToken();
|
|
88
|
+
if (!(0, boperators_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind))
|
|
89
|
+
continue;
|
|
90
|
+
const operandType = resolveOverloadedType(expression.getOperand(), overloadStore);
|
|
91
|
+
const overloadDesc = overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
|
|
92
|
+
if (!overloadDesc)
|
|
93
|
+
continue;
|
|
94
|
+
const exprStart = expression.getStart();
|
|
95
|
+
const operand = expression.getOperand();
|
|
96
|
+
edits.push({
|
|
97
|
+
operatorStart: exprStart,
|
|
98
|
+
operatorEnd: operand.getStart(),
|
|
99
|
+
hoverStart: exprStart,
|
|
100
|
+
hoverEnd: operand.getStart(),
|
|
101
|
+
exprStart,
|
|
102
|
+
exprEnd: expression.getEnd(),
|
|
103
|
+
className: overloadDesc.className,
|
|
104
|
+
classFilePath: overloadDesc.classFilePath,
|
|
105
|
+
operatorString: overloadDesc.operatorString,
|
|
106
|
+
returnType: overloadDesc.returnType,
|
|
107
|
+
operandType: operandType,
|
|
108
|
+
isStatic: overloadDesc.isStatic,
|
|
109
|
+
kind: "prefixUnary",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Scan postfix unary expressions
|
|
113
|
+
const postfixExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.PostfixUnaryExpression);
|
|
114
|
+
for (const expression of postfixExpressions) {
|
|
115
|
+
const operatorKind = expression.getOperatorToken();
|
|
116
|
+
if (!(0, boperators_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind))
|
|
117
|
+
continue;
|
|
118
|
+
const operandType = resolveOverloadedType(expression.getOperand(), overloadStore);
|
|
119
|
+
const overloadDesc = overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
|
|
120
|
+
if (!overloadDesc)
|
|
121
|
+
continue;
|
|
122
|
+
const operand = expression.getOperand();
|
|
123
|
+
const operatorStart = operand.getEnd();
|
|
124
|
+
edits.push({
|
|
125
|
+
operatorStart,
|
|
126
|
+
operatorEnd: expression.getEnd(),
|
|
127
|
+
hoverStart: operatorStart,
|
|
128
|
+
hoverEnd: expression.getEnd(),
|
|
129
|
+
exprStart: expression.getStart(),
|
|
130
|
+
exprEnd: expression.getEnd(),
|
|
131
|
+
className: overloadDesc.className,
|
|
132
|
+
classFilePath: overloadDesc.classFilePath,
|
|
133
|
+
operatorString: overloadDesc.operatorString,
|
|
134
|
+
returnType: overloadDesc.returnType,
|
|
135
|
+
operandType: operandType,
|
|
136
|
+
isStatic: overloadDesc.isStatic,
|
|
137
|
+
kind: "postfixUnary",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return edits;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Strip fully-qualified import paths from a type name so that
|
|
144
|
+
* `import("/path/to/Vec2").Vec2` is displayed as just `Vec2`.
|
|
145
|
+
*/
|
|
146
|
+
function simplifyTypeName(typeName) {
|
|
147
|
+
return typeName.replace(/\bimport\("[^"]*"\)\./g, "");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Build a QuickInfo response for hovering over an operator token
|
|
151
|
+
* that corresponds to an overloaded operator. Extracts the function
|
|
152
|
+
* signature and JSDoc from the overload definition.
|
|
153
|
+
*/
|
|
154
|
+
function getOverloadHoverInfo(ts, project, edit) {
|
|
155
|
+
var _a, _b, _c, _d;
|
|
156
|
+
try {
|
|
157
|
+
// Extract JSDoc from the method declaration (or its first overload signature).
|
|
158
|
+
let docText;
|
|
159
|
+
const classSourceFile = project.getSourceFile(edit.classFilePath);
|
|
160
|
+
if (classSourceFile) {
|
|
161
|
+
const classDecl = classSourceFile.getClass(edit.className);
|
|
162
|
+
if (classDecl) {
|
|
163
|
+
const method = classDecl
|
|
164
|
+
.getMethods()
|
|
165
|
+
.find((m) => (0, boperators_1.getOperatorStringFromMethod)(m) === edit.operatorString);
|
|
166
|
+
if (method) {
|
|
167
|
+
const overloads = method.getOverloads();
|
|
168
|
+
const source = overloads.length > 0 ? overloads[0] : method;
|
|
169
|
+
const jsDocs = source.getJsDocs();
|
|
170
|
+
if (jsDocs.length > 0) {
|
|
171
|
+
const raw = jsDocs[0].getText();
|
|
172
|
+
docText = raw
|
|
173
|
+
.replace(/^\/\*\*\s*/, "")
|
|
174
|
+
.replace(/\s*\*\/$/, "")
|
|
175
|
+
.replace(/^\s*\* ?/gm, "")
|
|
176
|
+
.trim();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Build display signature parts based on overload kind.
|
|
182
|
+
// Types are sourced from the resolved expression types stored at scan time.
|
|
183
|
+
const returnTypeName = simplifyTypeName(edit.returnType);
|
|
184
|
+
const displayParts = [];
|
|
185
|
+
if (edit.kind === "prefixUnary") {
|
|
186
|
+
// Prefix unary: "-Vec2 = Vec2"
|
|
187
|
+
displayParts.push({ text: edit.operatorString, kind: "operator" });
|
|
188
|
+
displayParts.push({
|
|
189
|
+
text: simplifyTypeName((_a = edit.operandType) !== null && _a !== void 0 ? _a : edit.className),
|
|
190
|
+
kind: "className",
|
|
191
|
+
});
|
|
192
|
+
if (returnTypeName !== "void") {
|
|
193
|
+
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
194
|
+
displayParts.push({ text: returnTypeName, kind: "className" });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (edit.kind === "postfixUnary") {
|
|
198
|
+
// Postfix unary: "Vec2++"
|
|
199
|
+
displayParts.push({ text: edit.className, kind: "className" });
|
|
200
|
+
displayParts.push({ text: edit.operatorString, kind: "operator" });
|
|
201
|
+
}
|
|
202
|
+
else if (edit.isStatic) {
|
|
203
|
+
// Binary static: "LhsType + RhsType = ReturnType"
|
|
204
|
+
displayParts.push({
|
|
205
|
+
text: simplifyTypeName((_b = edit.lhsType) !== null && _b !== void 0 ? _b : edit.className),
|
|
206
|
+
kind: "className",
|
|
207
|
+
});
|
|
208
|
+
displayParts.push({ text: " ", kind: "space" });
|
|
209
|
+
displayParts.push({ text: edit.operatorString, kind: "operator" });
|
|
210
|
+
displayParts.push({ text: " ", kind: "space" });
|
|
211
|
+
displayParts.push({
|
|
212
|
+
text: simplifyTypeName((_c = edit.rhsType) !== null && _c !== void 0 ? _c : edit.className),
|
|
213
|
+
kind: "className",
|
|
214
|
+
});
|
|
215
|
+
if (returnTypeName !== "void") {
|
|
216
|
+
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
217
|
+
displayParts.push({ text: returnTypeName, kind: "className" });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
// Binary instance: "ClassName += RhsType"
|
|
222
|
+
displayParts.push({ text: edit.className, kind: "className" });
|
|
223
|
+
displayParts.push({ text: " ", kind: "space" });
|
|
224
|
+
displayParts.push({ text: edit.operatorString, kind: "operator" });
|
|
225
|
+
displayParts.push({ text: " ", kind: "space" });
|
|
226
|
+
displayParts.push({
|
|
227
|
+
text: simplifyTypeName((_d = edit.rhsType) !== null && _d !== void 0 ? _d : "unknown"),
|
|
228
|
+
kind: "className",
|
|
229
|
+
});
|
|
230
|
+
if (returnTypeName !== "void") {
|
|
231
|
+
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
232
|
+
displayParts.push({ text: returnTypeName, kind: "className" });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
kind: ts.ScriptElementKind.functionElement,
|
|
237
|
+
kindModifiers: edit.isStatic ? "static" : "",
|
|
238
|
+
textSpan: {
|
|
239
|
+
start: edit.operatorStart,
|
|
240
|
+
length: edit.operatorEnd - edit.operatorStart,
|
|
241
|
+
},
|
|
242
|
+
displayParts,
|
|
243
|
+
documentation: docText ? [{ text: docText, kind: "text" }] : undefined,
|
|
244
|
+
tags: [],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (_e) {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,291 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const boperators_1 = require("boperators");
|
|
3
|
+
const helpers_1 = require("./helpers");
|
|
3
4
|
const SourceMap_1 = require("./SourceMap");
|
|
4
|
-
// ----- Overload edit scanner -----
|
|
5
|
-
/**
|
|
6
|
-
* Before transformation, find all expressions (binary, prefix unary, postfix unary)
|
|
7
|
-
* that match registered overloads and record their operator token positions.
|
|
8
|
-
* This is used to provide hover info for overloaded operators.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* Recursively resolve the effective type of an expression, accounting for
|
|
12
|
-
* operator overloads. For sub-expressions that match a registered overload,
|
|
13
|
-
* uses the overload's declared return type instead of what TypeScript infers
|
|
14
|
-
* (since TS doesn't know about operator overloading).
|
|
15
|
-
*/
|
|
16
|
-
function resolveOverloadedType(node, overloadStore) {
|
|
17
|
-
if (boperators_1.Node.isParenthesizedExpression(node)) {
|
|
18
|
-
return resolveOverloadedType(node.getExpression(), overloadStore);
|
|
19
|
-
}
|
|
20
|
-
if (boperators_1.Node.isBinaryExpression(node)) {
|
|
21
|
-
const operatorKind = node.getOperatorToken().getKind();
|
|
22
|
-
if ((0, boperators_1.isOperatorSyntaxKind)(operatorKind)) {
|
|
23
|
-
const leftType = resolveOverloadedType(node.getLeft(), overloadStore);
|
|
24
|
-
const rightType = resolveOverloadedType(node.getRight(), overloadStore);
|
|
25
|
-
const overload = overloadStore.findOverload(operatorKind, leftType, rightType);
|
|
26
|
-
if (overload)
|
|
27
|
-
return overload.returnType;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (boperators_1.Node.isPrefixUnaryExpression(node)) {
|
|
31
|
-
const operatorKind = node.getOperatorToken();
|
|
32
|
-
if ((0, boperators_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind)) {
|
|
33
|
-
const operandType = resolveOverloadedType(node.getOperand(), overloadStore);
|
|
34
|
-
const overload = overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
|
|
35
|
-
if (overload)
|
|
36
|
-
return overload.returnType;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (boperators_1.Node.isPostfixUnaryExpression(node)) {
|
|
40
|
-
const operatorKind = node.getOperatorToken();
|
|
41
|
-
if ((0, boperators_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind)) {
|
|
42
|
-
const operandType = resolveOverloadedType(node.getOperand(), overloadStore);
|
|
43
|
-
const overload = overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
|
|
44
|
-
if (overload)
|
|
45
|
-
return overload.returnType;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return (0, boperators_1.resolveExpressionType)(node);
|
|
49
|
-
}
|
|
50
|
-
function findOverloadEdits(sourceFile, overloadStore) {
|
|
51
|
-
const edits = [];
|
|
52
|
-
const binaryExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.BinaryExpression);
|
|
53
|
-
for (const expression of binaryExpressions) {
|
|
54
|
-
const operatorToken = expression.getOperatorToken();
|
|
55
|
-
const operatorKind = operatorToken.getKind();
|
|
56
|
-
if (!(0, boperators_1.isOperatorSyntaxKind)(operatorKind))
|
|
57
|
-
continue;
|
|
58
|
-
const leftType = resolveOverloadedType(expression.getLeft(), overloadStore);
|
|
59
|
-
const rightType = resolveOverloadedType(expression.getRight(), overloadStore);
|
|
60
|
-
const overloadDesc = overloadStore.findOverload(operatorKind, leftType, rightType);
|
|
61
|
-
if (!overloadDesc)
|
|
62
|
-
continue;
|
|
63
|
-
edits.push({
|
|
64
|
-
operatorStart: operatorToken.getStart(),
|
|
65
|
-
operatorEnd: operatorToken.getEnd(),
|
|
66
|
-
hoverStart: expression.getLeft().getEnd(),
|
|
67
|
-
hoverEnd: expression.getRight().getStart(),
|
|
68
|
-
exprStart: expression.getStart(),
|
|
69
|
-
exprEnd: expression.getEnd(),
|
|
70
|
-
className: overloadDesc.className,
|
|
71
|
-
classFilePath: overloadDesc.classFilePath,
|
|
72
|
-
operatorString: overloadDesc.operatorString,
|
|
73
|
-
index: overloadDesc.index,
|
|
74
|
-
isStatic: overloadDesc.isStatic,
|
|
75
|
-
kind: "binary",
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
// Scan prefix unary expressions
|
|
79
|
-
const prefixExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.PrefixUnaryExpression);
|
|
80
|
-
for (const expression of prefixExpressions) {
|
|
81
|
-
const operatorKind = expression.getOperatorToken();
|
|
82
|
-
if (!(0, boperators_1.isPrefixUnaryOperatorSyntaxKind)(operatorKind))
|
|
83
|
-
continue;
|
|
84
|
-
const operandType = resolveOverloadedType(expression.getOperand(), overloadStore);
|
|
85
|
-
const overloadDesc = overloadStore.findPrefixUnaryOverload(operatorKind, operandType);
|
|
86
|
-
if (!overloadDesc)
|
|
87
|
-
continue;
|
|
88
|
-
const exprStart = expression.getStart();
|
|
89
|
-
const operand = expression.getOperand();
|
|
90
|
-
edits.push({
|
|
91
|
-
operatorStart: exprStart,
|
|
92
|
-
operatorEnd: operand.getStart(),
|
|
93
|
-
hoverStart: exprStart,
|
|
94
|
-
hoverEnd: operand.getStart(),
|
|
95
|
-
exprStart,
|
|
96
|
-
exprEnd: expression.getEnd(),
|
|
97
|
-
className: overloadDesc.className,
|
|
98
|
-
classFilePath: overloadDesc.classFilePath,
|
|
99
|
-
operatorString: overloadDesc.operatorString,
|
|
100
|
-
index: overloadDesc.index,
|
|
101
|
-
isStatic: overloadDesc.isStatic,
|
|
102
|
-
kind: "prefixUnary",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
// Scan postfix unary expressions
|
|
106
|
-
const postfixExpressions = sourceFile.getDescendantsOfKind(boperators_1.SyntaxKind.PostfixUnaryExpression);
|
|
107
|
-
for (const expression of postfixExpressions) {
|
|
108
|
-
const operatorKind = expression.getOperatorToken();
|
|
109
|
-
if (!(0, boperators_1.isPostfixUnaryOperatorSyntaxKind)(operatorKind))
|
|
110
|
-
continue;
|
|
111
|
-
const operandType = resolveOverloadedType(expression.getOperand(), overloadStore);
|
|
112
|
-
const overloadDesc = overloadStore.findPostfixUnaryOverload(operatorKind, operandType);
|
|
113
|
-
if (!overloadDesc)
|
|
114
|
-
continue;
|
|
115
|
-
const operand = expression.getOperand();
|
|
116
|
-
const operatorStart = operand.getEnd();
|
|
117
|
-
edits.push({
|
|
118
|
-
operatorStart,
|
|
119
|
-
operatorEnd: expression.getEnd(),
|
|
120
|
-
hoverStart: operatorStart,
|
|
121
|
-
hoverEnd: expression.getEnd(),
|
|
122
|
-
exprStart: expression.getStart(),
|
|
123
|
-
exprEnd: expression.getEnd(),
|
|
124
|
-
className: overloadDesc.className,
|
|
125
|
-
classFilePath: overloadDesc.classFilePath,
|
|
126
|
-
operatorString: overloadDesc.operatorString,
|
|
127
|
-
index: overloadDesc.index,
|
|
128
|
-
isStatic: overloadDesc.isStatic,
|
|
129
|
-
kind: "postfixUnary",
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return edits;
|
|
133
|
-
}
|
|
134
|
-
// ----- Overload hover info -----
|
|
135
|
-
/**
|
|
136
|
-
* Build a QuickInfo response for hovering over an operator token
|
|
137
|
-
* that corresponds to an overloaded operator. Extracts the function
|
|
138
|
-
* signature and JSDoc from the overload definition.
|
|
139
|
-
*/
|
|
140
|
-
function getOverloadHoverInfo(ts, project, edit) {
|
|
141
|
-
try {
|
|
142
|
-
const classSourceFile = project.getSourceFile(edit.classFilePath);
|
|
143
|
-
if (!classSourceFile)
|
|
144
|
-
return undefined;
|
|
145
|
-
const classDecl = classSourceFile.getClass(edit.className);
|
|
146
|
-
if (!classDecl)
|
|
147
|
-
return undefined;
|
|
148
|
-
// Find the property with the matching operator string
|
|
149
|
-
const prop = classDecl.getProperties().find((p) => {
|
|
150
|
-
if (!boperators_1.Node.isPropertyDeclaration(p))
|
|
151
|
-
return false;
|
|
152
|
-
return (0, boperators_1.getOperatorStringFromProperty)(p) === edit.operatorString;
|
|
153
|
-
});
|
|
154
|
-
if (!prop || !boperators_1.Node.isPropertyDeclaration(prop))
|
|
155
|
-
return undefined;
|
|
156
|
-
// Extract param types and return type from either the initializer (regular
|
|
157
|
-
// .ts files) or the type annotation (.d.ts files where initializers are
|
|
158
|
-
// stripped by TypeScript's declaration emit).
|
|
159
|
-
let params = [];
|
|
160
|
-
let returnTypeName;
|
|
161
|
-
let docText;
|
|
162
|
-
const initializer = (0, boperators_1.unwrapInitializer)(prop.getInitializer());
|
|
163
|
-
if (initializer && boperators_1.Node.isArrayLiteralExpression(initializer)) {
|
|
164
|
-
const element = initializer.getElements()[edit.index];
|
|
165
|
-
if (!element ||
|
|
166
|
-
(!boperators_1.Node.isFunctionExpression(element) && !boperators_1.Node.isArrowFunction(element)))
|
|
167
|
-
return undefined;
|
|
168
|
-
const nonThisParams = element
|
|
169
|
-
.getParameters()
|
|
170
|
-
.filter((p) => p.getName() !== "this");
|
|
171
|
-
params = nonThisParams.map((p) => ({
|
|
172
|
-
typeName: p.getType().getText(element),
|
|
173
|
-
}));
|
|
174
|
-
returnTypeName = element.getReturnType().getText(element);
|
|
175
|
-
const jsDocs = element.getJsDocs();
|
|
176
|
-
if (jsDocs.length > 0) {
|
|
177
|
-
const raw = jsDocs[0].getText();
|
|
178
|
-
docText = raw
|
|
179
|
-
.replace(/^\/\*\*\s*/, "")
|
|
180
|
-
.replace(/\s*\*\/$/, "")
|
|
181
|
-
.replace(/^\s*\* ?/gm, "")
|
|
182
|
-
.trim();
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
// Type-annotation fallback for .d.ts files
|
|
187
|
-
const propertyType = prop.getType();
|
|
188
|
-
if (!propertyType.isTuple())
|
|
189
|
-
return undefined;
|
|
190
|
-
const tupleElements = propertyType.getTupleElements();
|
|
191
|
-
if (edit.index >= tupleElements.length)
|
|
192
|
-
return undefined;
|
|
193
|
-
const elementType = tupleElements[edit.index];
|
|
194
|
-
const callSigs = elementType.getCallSignatures();
|
|
195
|
-
if (callSigs.length === 0)
|
|
196
|
-
return undefined;
|
|
197
|
-
const sig = callSigs[0];
|
|
198
|
-
for (const sym of sig.getParameters()) {
|
|
199
|
-
if (sym.getName() === "this")
|
|
200
|
-
continue;
|
|
201
|
-
const decl = sym.getValueDeclaration();
|
|
202
|
-
if (!decl)
|
|
203
|
-
continue;
|
|
204
|
-
params.push({ typeName: decl.getType().getText(prop) });
|
|
205
|
-
}
|
|
206
|
-
returnTypeName = sig.getReturnType().getText(prop);
|
|
207
|
-
}
|
|
208
|
-
// Build display signature parts based on overload kind
|
|
209
|
-
const displayParts = [];
|
|
210
|
-
if (edit.kind === "prefixUnary") {
|
|
211
|
-
// Prefix unary: "-Vector3 = Vector3"
|
|
212
|
-
displayParts.push({
|
|
213
|
-
text: edit.operatorString,
|
|
214
|
-
kind: "operator",
|
|
215
|
-
});
|
|
216
|
-
const operandType = params.length >= 1 ? params[0].typeName : edit.className;
|
|
217
|
-
displayParts.push({ text: operandType, kind: "className" });
|
|
218
|
-
if (returnTypeName !== "void") {
|
|
219
|
-
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
220
|
-
displayParts.push({
|
|
221
|
-
text: returnTypeName,
|
|
222
|
-
kind: "className",
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else if (edit.kind === "postfixUnary") {
|
|
227
|
-
// Postfix unary: "Vector3++"
|
|
228
|
-
displayParts.push({ text: edit.className, kind: "className" });
|
|
229
|
-
displayParts.push({
|
|
230
|
-
text: edit.operatorString,
|
|
231
|
-
kind: "operator",
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
else if (edit.isStatic && params.length >= 2) {
|
|
235
|
-
// Binary static: "LhsType + RhsType = ReturnType"
|
|
236
|
-
const lhsType = params[0].typeName;
|
|
237
|
-
const rhsType = params[1].typeName;
|
|
238
|
-
displayParts.push({ text: lhsType, kind: "className" });
|
|
239
|
-
displayParts.push({ text: " ", kind: "space" });
|
|
240
|
-
displayParts.push({
|
|
241
|
-
text: edit.operatorString,
|
|
242
|
-
kind: "operator",
|
|
243
|
-
});
|
|
244
|
-
displayParts.push({ text: " ", kind: "space" });
|
|
245
|
-
displayParts.push({ text: rhsType, kind: "className" });
|
|
246
|
-
if (returnTypeName !== "void") {
|
|
247
|
-
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
248
|
-
displayParts.push({
|
|
249
|
-
text: returnTypeName,
|
|
250
|
-
kind: "className",
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
// Binary instance: "ClassName += RhsType"
|
|
256
|
-
const rhsType = params.length >= 1 ? params[0].typeName : "unknown";
|
|
257
|
-
displayParts.push({ text: edit.className, kind: "className" });
|
|
258
|
-
displayParts.push({ text: " ", kind: "space" });
|
|
259
|
-
displayParts.push({
|
|
260
|
-
text: edit.operatorString,
|
|
261
|
-
kind: "operator",
|
|
262
|
-
});
|
|
263
|
-
displayParts.push({ text: " ", kind: "space" });
|
|
264
|
-
displayParts.push({ text: rhsType, kind: "className" });
|
|
265
|
-
if (returnTypeName !== "void") {
|
|
266
|
-
displayParts.push({ text: " = ", kind: "punctuation" });
|
|
267
|
-
displayParts.push({
|
|
268
|
-
text: returnTypeName,
|
|
269
|
-
kind: "className",
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
kind: ts.ScriptElementKind.functionElement,
|
|
275
|
-
kindModifiers: edit.isStatic ? "static" : "",
|
|
276
|
-
textSpan: {
|
|
277
|
-
start: edit.operatorStart,
|
|
278
|
-
length: edit.operatorEnd - edit.operatorStart,
|
|
279
|
-
},
|
|
280
|
-
displayParts,
|
|
281
|
-
documentation: docText ? [{ text: docText, kind: "text" }] : undefined,
|
|
282
|
-
tags: [],
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
catch (_a) {
|
|
286
|
-
return undefined;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
5
|
// ----- LanguageService proxy -----
|
|
290
6
|
function getSourceMapForFile(cache, fileName) {
|
|
291
7
|
const entry = cache.get(fileName);
|
|
@@ -293,15 +9,14 @@ function getSourceMapForFile(cache, fileName) {
|
|
|
293
9
|
return undefined;
|
|
294
10
|
return entry.sourceMap;
|
|
295
11
|
}
|
|
296
|
-
function
|
|
297
|
-
if (diag.start
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
12
|
+
function withRemappedSpan(diag, sourceMap) {
|
|
13
|
+
if (diag.start === undefined || diag.length === undefined)
|
|
14
|
+
return diag;
|
|
15
|
+
const remapped = sourceMap.remapSpan({
|
|
16
|
+
start: diag.start,
|
|
17
|
+
length: diag.length,
|
|
18
|
+
});
|
|
19
|
+
return Object.assign(Object.assign({}, diag), { start: remapped.start, length: remapped.length });
|
|
305
20
|
}
|
|
306
21
|
function createProxy(ts, ls, cache, project) {
|
|
307
22
|
// Copy all methods from the underlying language service
|
|
@@ -320,55 +35,37 @@ function createProxy(ts, ls, cache, project) {
|
|
|
320
35
|
}
|
|
321
36
|
return false;
|
|
322
37
|
};
|
|
38
|
+
function remapDiagnostics(result, entry) {
|
|
39
|
+
const sourceMap = (entry === null || entry === void 0 ? void 0 : entry.sourceMap.isEmpty) === false ? entry.sourceMap : undefined;
|
|
40
|
+
if (!sourceMap)
|
|
41
|
+
return result;
|
|
42
|
+
return result.map((diag) => {
|
|
43
|
+
var _a;
|
|
44
|
+
const remapped = withRemappedSpan(diag, sourceMap);
|
|
45
|
+
if (!((_a = remapped.relatedInformation) === null || _a === void 0 ? void 0 : _a.length))
|
|
46
|
+
return remapped;
|
|
47
|
+
return Object.assign(Object.assign({}, remapped), { relatedInformation: remapped.relatedInformation.map((related) => {
|
|
48
|
+
const relatedMap = related.file
|
|
49
|
+
? getSourceMapForFile(cache, related.file.fileName)
|
|
50
|
+
: undefined;
|
|
51
|
+
return relatedMap ? withRemappedSpan(related, relatedMap) : related;
|
|
52
|
+
}) });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
323
55
|
proxy.getSemanticDiagnostics = (fileName) => {
|
|
324
56
|
const result = ls.getSemanticDiagnostics(fileName);
|
|
325
57
|
const entry = cache.get(fileName);
|
|
326
|
-
|
|
327
|
-
if (sourceMap) {
|
|
328
|
-
for (const diag of result) {
|
|
329
|
-
remapDiagnosticSpan(diag, sourceMap);
|
|
330
|
-
if (diag.relatedInformation) {
|
|
331
|
-
for (const related of diag.relatedInformation) {
|
|
332
|
-
const relatedMap = related.file
|
|
333
|
-
? getSourceMapForFile(cache, related.file.fileName)
|
|
334
|
-
: undefined;
|
|
335
|
-
if (relatedMap)
|
|
336
|
-
remapDiagnosticSpan(related, relatedMap);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return result.filter((diag) => !isOverloadSuppressed(diag.code, diag.start, entry));
|
|
58
|
+
return remapDiagnostics(result, entry).filter((diag) => !isOverloadSuppressed(diag.code, diag.start, entry));
|
|
342
59
|
};
|
|
343
60
|
proxy.getSyntacticDiagnostics = (fileName) => {
|
|
344
61
|
const result = ls.getSyntacticDiagnostics(fileName);
|
|
345
62
|
const entry = cache.get(fileName);
|
|
346
|
-
|
|
347
|
-
if (sourceMap) {
|
|
348
|
-
for (const diag of result) {
|
|
349
|
-
remapDiagnosticSpan(diag, sourceMap);
|
|
350
|
-
if (diag.relatedInformation) {
|
|
351
|
-
for (const related of diag.relatedInformation) {
|
|
352
|
-
const relatedMap = related.file
|
|
353
|
-
? getSourceMapForFile(cache, related.file.fileName)
|
|
354
|
-
: undefined;
|
|
355
|
-
if (relatedMap)
|
|
356
|
-
remapDiagnosticSpan(related, relatedMap);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return result.filter((diag) => !isOverloadSuppressed(diag.code, diag.start, entry));
|
|
63
|
+
return remapDiagnostics(result, entry).filter((diag) => !isOverloadSuppressed(diag.code, diag.start, entry));
|
|
362
64
|
};
|
|
363
65
|
proxy.getSuggestionDiagnostics = (fileName) => {
|
|
364
66
|
const result = ls.getSuggestionDiagnostics(fileName);
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
return result;
|
|
368
|
-
for (const diag of result) {
|
|
369
|
-
remapDiagnosticSpan(diag, sourceMap);
|
|
370
|
-
}
|
|
371
|
-
return result;
|
|
67
|
+
const entry = cache.get(fileName);
|
|
68
|
+
return remapDiagnostics(result, entry);
|
|
372
69
|
};
|
|
373
70
|
// --- Hover: remap input position + output span, custom operator hover ---
|
|
374
71
|
proxy.getQuickInfoAtPosition = (fileName, position) => {
|
|
@@ -377,7 +74,7 @@ function createProxy(ts, ls, cache, project) {
|
|
|
377
74
|
if (entry) {
|
|
378
75
|
const operatorEdit = entry.overloadEdits.find((e) => position >= e.hoverStart && position < e.hoverEnd);
|
|
379
76
|
if (operatorEdit) {
|
|
380
|
-
const hoverInfo = getOverloadHoverInfo(ts, project, operatorEdit);
|
|
77
|
+
const hoverInfo = (0, helpers_1.getOverloadHoverInfo)(ts, project, operatorEdit);
|
|
381
78
|
if (hoverInfo)
|
|
382
79
|
return hoverInfo;
|
|
383
80
|
}
|
|
@@ -666,7 +363,7 @@ module.exports = function init(modules) {
|
|
|
666
363
|
// Before transforming, scan for overloaded expressions
|
|
667
364
|
// so we can record their operator positions for hover info.
|
|
668
365
|
const sourceFile = project.getSourceFileOrThrow(fileName);
|
|
669
|
-
const overloadEdits = findOverloadEdits(sourceFile, overloadStore);
|
|
366
|
+
const overloadEdits = (0, helpers_1.findOverloadEdits)(sourceFile, overloadStore);
|
|
670
367
|
// Transform expressions (returns text + source map)
|
|
671
368
|
const result = overloadInjector.overloadFile(fileName);
|
|
672
369
|
cache.set(fileName, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boperators/plugin-ts-language-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "TypeScript Language Server plugin for boperators - IDE support with source mapping.",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsc",
|
|
29
29
|
"watch": "tsc --watch",
|
|
30
|
+
"test": "bun test",
|
|
30
31
|
"prepublish": "bun run build"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"dist"
|
|
37
38
|
],
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"boperators": "0.
|
|
40
|
+
"boperators": "0.3.0",
|
|
40
41
|
"typescript": ">=5.0.0 <5.10.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|