luaut-language-server 4.0.0 → 5.0.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/README.md +10 -1
- package/dist/{chunk-XK3KS3T4.js → chunk-GHAR7NOH.js} +253 -53
- package/dist/chunk-GHAR7NOH.js.map +1 -0
- package/dist/cli.cjs +266 -66
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +266 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-XK3KS3T4.js.map +0 -1
package/README.md
CHANGED
|
@@ -18,11 +18,12 @@ luaut-language-server --stdio
|
|
|
18
18
|
|---|---|
|
|
19
19
|
| `publishDiagnostics` | syntax, scope (redeclare, assign-to-`const`) and type errors, on open and on every keystroke. A name nothing declares is an error ("Cannot find name 'x'") whenever type libraries are loaded. `--@luaut-nocheck`, `--@luaut-ignore` and `--@luaut-expect-error` silence scope and type errors |
|
|
20
20
|
| `hover` | the type as luaut writes it — the **narrowed** type at a reference, so a guarded `v` reads `string`, not `string \| nil`. Also every name in a type or definitions file: `declare` names (with their overload count), classes (`declare class Part extends BasePart { ...what it adds }`), alias names, object-type properties, type parameters, `infer` names, and any type annotation, which reads as what it resolves to |
|
|
21
|
+
| `luaut/hover` | the same hover, at a level the editor asks for (`depth`), and whether there is another (`canExpand`). Level 0 is the shortest true reading — names left as names — and each one opens the names standing a step further in: `const b: Shape`, then `{ kind: "circle", size: number }`, then whatever those are named after. A class stays its name, and a type that names itself opens once. LSP has no way to ask for this, so every other editor gets level 0 through `hover` |
|
|
21
22
|
| `semanticTokens` | colours from the parser, not from patterns — see [Highlighting](#highlighting) |
|
|
22
23
|
| `definition` | the binding's declaration — and from an `import`, the export in the other module |
|
|
23
24
|
| `references`, `documentHighlight` | every use of the binding |
|
|
24
25
|
| `rename`, `prepareRename` | refuses names that are not identifiers, and builtins from the definitions files |
|
|
25
|
-
| `completion` | members after `.` / `:` (never the globals there), names in scope, type names in a type position; inside an `import`, module paths and the exported names. Inside an object literal written against a type — an annotation, `satisfies`, an argument — the keys that type names, minus the ones already there. A name another file of the project exports is offered too, and picking it adds `import { name } from "./path"` at the top (or joins the import of that file already there). With the Roblox types, each service is offered, and picking one adds `const Players = game:GetService("Players")` under the imports and the services already declared |
|
|
26
|
+
| `completion` | members after `.` / `:` (never the globals there), names in scope — including what hoisting makes visible before its declaration: a function declaration anywhere in its block, and every name of the module inside a function body — type names in a type position; inside an `import`, module paths and the exported names. Inside an object literal written against a type — an annotation, `satisfies`, an argument — the keys that type names, minus the ones already there. A name another file of the project exports is offered too, and picking it adds `import { name } from "./path"` at the top (or joins the import of that file already there). With the Roblox types, each service is offered, and picking one adds `const Players = game:GetService("Players")` under the imports and the services already declared |
|
|
26
27
|
| `signatureHelp` | every overload, with the active parameter — `:` calls count `self` for you |
|
|
27
28
|
| `documentSymbol` | functions, type aliases, top-level bindings |
|
|
28
29
|
|
|
@@ -91,6 +92,14 @@ name. The grammar in the editor extension keeps just what characters decide
|
|
|
91
92
|
alone — comments, strings, numbers, reserved words — so a file looks right
|
|
92
93
|
before the server answers, and never disagrees with it after.
|
|
93
94
|
|
|
95
|
+
### Saying more
|
|
96
|
+
|
|
97
|
+
A hover opens with the shortest thing that is true and says more when asked,
|
|
98
|
+
as TypeScript's does. The server answers `luaut/hover` at whatever level it is
|
|
99
|
+
given; how the editor offers the next one is the editor's business. The VS
|
|
100
|
+
Code extension puts a link under the type, because the hover API that would
|
|
101
|
+
draw the buttons is still a proposed one.
|
|
102
|
+
|
|
94
103
|
## Not yet
|
|
95
104
|
|
|
96
105
|
- **One file at a time.** No workspace indexing, so no cross-file
|
|
@@ -68,7 +68,8 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
|
68
68
|
}
|
|
69
69
|
function takesSelf(type) {
|
|
70
70
|
for (const signature of signaturesOf(type)) {
|
|
71
|
-
|
|
71
|
+
const first = signature.params[0]?.name;
|
|
72
|
+
if (first === "self" || first === "this") return true;
|
|
72
73
|
}
|
|
73
74
|
return false;
|
|
74
75
|
}
|
|
@@ -89,7 +90,7 @@ function signatureLabel(signature) {
|
|
|
89
90
|
});
|
|
90
91
|
const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
|
|
91
92
|
const varargs = signature.varargs ? [`...: ${formatType(signature.varargs)}`] : [];
|
|
92
|
-
const label = `${generics}(${[...parameters, ...varargs].join(", ")})
|
|
93
|
+
const label = `${generics}(${[...parameters, ...varargs].join(", ")}) => ${formatType(signature.returns)}`;
|
|
93
94
|
return { label, parameters };
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -697,7 +698,7 @@ function exportDeclaration(analyzer, module, name, seen = /* @__PURE__ */ new Se
|
|
|
697
698
|
break;
|
|
698
699
|
case "ExportStatement": {
|
|
699
700
|
const declaration = statement.declaration;
|
|
700
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
701
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
|
|
701
702
|
if (declaration.name.name === name) return here(declaration.name);
|
|
702
703
|
} else {
|
|
703
704
|
for (const target of declaration.names) {
|
|
@@ -811,17 +812,101 @@ function diagnostics(analysis) {
|
|
|
811
812
|
// src/features/hover.ts
|
|
812
813
|
import {
|
|
813
814
|
formatType as formatType3,
|
|
814
|
-
isClassType
|
|
815
|
+
isClassType as isClassType2
|
|
815
816
|
} from "luaut-parser";
|
|
816
|
-
|
|
817
|
+
|
|
818
|
+
// src/features/expand.ts
|
|
819
|
+
import { isClassType, arrayOf, tuple, objectType, fn, union as union2, intersection } from "luaut-parser";
|
|
820
|
+
function expandAliases(type, aliases, depth, seen = []) {
|
|
821
|
+
if (depth <= 0) return type;
|
|
822
|
+
const name = withheldName(type);
|
|
823
|
+
if (name !== void 0 && !seen.includes(name)) {
|
|
824
|
+
const opened = aliases.get(name) ?? type;
|
|
825
|
+
const inner = [...seen, name];
|
|
826
|
+
return children2(unnamed(opened), (t) => expandAliases(t, aliases, depth - 1, inner));
|
|
827
|
+
}
|
|
828
|
+
return children2(type, (t) => expandAliases(t, aliases, depth, seen));
|
|
829
|
+
}
|
|
830
|
+
function withheldName(type) {
|
|
831
|
+
if (isClassType(type)) return void 0;
|
|
832
|
+
if (type.kind === "object" || type.kind === "intersection") return type.name;
|
|
833
|
+
if (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
|
|
834
|
+
return void 0;
|
|
835
|
+
}
|
|
836
|
+
function unnamed(type) {
|
|
837
|
+
if (type.kind === "object") {
|
|
838
|
+
const out = objectType(type.properties, type.indexer, type.frozen);
|
|
839
|
+
return type.class ? Object.assign(out, { class: type.class, name: type.name }) : out;
|
|
840
|
+
}
|
|
841
|
+
if (type.kind === "intersection" && type.name) return { ...type, name: void 0 };
|
|
842
|
+
return type;
|
|
843
|
+
}
|
|
844
|
+
function children2(type, f) {
|
|
845
|
+
switch (type.kind) {
|
|
846
|
+
case "array":
|
|
847
|
+
return arrayOf(f(type.element));
|
|
848
|
+
case "tuple":
|
|
849
|
+
return tuple(type.elements.map(f), type.isPack);
|
|
850
|
+
case "union":
|
|
851
|
+
return union2(type.types.map(f));
|
|
852
|
+
case "intersection": {
|
|
853
|
+
const out = intersection(type.types.map(f));
|
|
854
|
+
return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
|
|
855
|
+
}
|
|
856
|
+
case "object": {
|
|
857
|
+
if (isClassType(type)) return type;
|
|
858
|
+
const out = objectType(
|
|
859
|
+
[...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
|
|
860
|
+
type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
|
|
861
|
+
type.frozen
|
|
862
|
+
);
|
|
863
|
+
if (type.name) out.name = type.name;
|
|
864
|
+
return out;
|
|
865
|
+
}
|
|
866
|
+
case "function":
|
|
867
|
+
return fn(
|
|
868
|
+
type.params.map((p) => ({ ...p, type: f(p.type) })),
|
|
869
|
+
f(type.returns),
|
|
870
|
+
type.varargs && f(type.varargs),
|
|
871
|
+
type.typeParams,
|
|
872
|
+
type.predicate
|
|
873
|
+
);
|
|
874
|
+
default:
|
|
875
|
+
return type;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/features/hover.ts
|
|
880
|
+
function hover(analysis, position, depth = 0) {
|
|
817
881
|
const path = pathAt(analysis.program, position, true);
|
|
818
882
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
819
883
|
if (UNNAMED.has(path[i].type)) return null;
|
|
820
|
-
const text =
|
|
821
|
-
if (text)
|
|
884
|
+
const text = at(analysis, path, i, depth);
|
|
885
|
+
if (!text) continue;
|
|
886
|
+
const canExpand = at(analysis, path, i, depth + 1) !== text;
|
|
887
|
+
return {
|
|
888
|
+
contents: { kind: "markdown", value: code(text) },
|
|
889
|
+
range: toRange(path[i]),
|
|
890
|
+
depth,
|
|
891
|
+
canExpand
|
|
892
|
+
};
|
|
822
893
|
}
|
|
823
894
|
return null;
|
|
824
895
|
}
|
|
896
|
+
function at(analysis, path, index, depth) {
|
|
897
|
+
const previous = expansion;
|
|
898
|
+
expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
|
|
899
|
+
try {
|
|
900
|
+
return describe(analysis, path, index);
|
|
901
|
+
} finally {
|
|
902
|
+
expansion = previous;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
var expansion = {
|
|
906
|
+
depth: 0,
|
|
907
|
+
aliases: /* @__PURE__ */ new Map(),
|
|
908
|
+
source: ""
|
|
909
|
+
};
|
|
825
910
|
var UNNAMED = /* @__PURE__ */ new Set([
|
|
826
911
|
"BinaryExpression",
|
|
827
912
|
"UnaryExpression",
|
|
@@ -854,8 +939,8 @@ function describe(analysis, path, index) {
|
|
|
854
939
|
case "TableExpression": {
|
|
855
940
|
const field = fieldWithKey(parent, node);
|
|
856
941
|
if (!field) break;
|
|
857
|
-
const
|
|
858
|
-
const property =
|
|
942
|
+
const objectType2 = types.typeOf.get(parent);
|
|
943
|
+
const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
|
|
859
944
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
860
945
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
861
946
|
}
|
|
@@ -903,6 +988,19 @@ function describe(analysis, path, index) {
|
|
|
903
988
|
case "DeclareClassStatement":
|
|
904
989
|
if (parent.name === node) return classText(analysis, name);
|
|
905
990
|
break;
|
|
991
|
+
case "ClassDeclaration":
|
|
992
|
+
if (parent.name === node) return classText(analysis, name);
|
|
993
|
+
break;
|
|
994
|
+
case "ClassField":
|
|
995
|
+
if (parent.name === node) {
|
|
996
|
+
const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
|
|
997
|
+
const prefix = parent.isStatic ? "(static) " : "(field) ";
|
|
998
|
+
return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
|
|
999
|
+
}
|
|
1000
|
+
break;
|
|
1001
|
+
case "ClassAccessor":
|
|
1002
|
+
if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
|
|
1003
|
+
break;
|
|
906
1004
|
case "TableTypeProperty":
|
|
907
1005
|
if (parent.key === node) {
|
|
908
1006
|
const type2 = typeOfNode(parent.valueType);
|
|
@@ -953,7 +1051,7 @@ function describe(analysis, path, index) {
|
|
|
953
1051
|
case "TypedIdentifier": {
|
|
954
1052
|
const binding = bindingOfNode(analysis, node);
|
|
955
1053
|
const type2 = binding && types.bindingType.get(binding.id);
|
|
956
|
-
return type2 ? bindingText(binding, type2) : void 0;
|
|
1054
|
+
return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
|
|
957
1055
|
}
|
|
958
1056
|
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
959
1057
|
// parameter in scope.
|
|
@@ -967,7 +1065,7 @@ function describe(analysis, path, index) {
|
|
|
967
1065
|
if (!node.typeArguments.length) {
|
|
968
1066
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
969
1067
|
const alias = types.aliases.get(qualified);
|
|
970
|
-
if (alias &&
|
|
1068
|
+
if (alias && isClassType2(alias)) return classText(analysis, qualified);
|
|
971
1069
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
972
1070
|
}
|
|
973
1071
|
const type2 = typeOfNode(node);
|
|
@@ -999,16 +1097,25 @@ function declareText(analysis, statement) {
|
|
|
999
1097
|
}
|
|
1000
1098
|
function classText(analysis, name) {
|
|
1001
1099
|
const type = analysis.types.aliases.get(name);
|
|
1002
|
-
if (!type || !
|
|
1100
|
+
if (!type || !isClassType2(type)) return void 0;
|
|
1003
1101
|
const superclass = type.class.superclass;
|
|
1004
1102
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
1005
|
-
const own = [...type.properties].filter(([key, property]) =>
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1103
|
+
const own = [...type.properties].filter(([key, property]) => (
|
|
1104
|
+
// `ClassObject` is on every instance and says nothing about this one.
|
|
1105
|
+
key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
|
|
1106
|
+
));
|
|
1107
|
+
const written = isRuntimeClass(analysis, name);
|
|
1108
|
+
const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
|
|
1109
|
+
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${formatType3(property.type)}${written ? "" : ","}`);
|
|
1110
|
+
return own.length ? `${head} {
|
|
1010
1111
|
${lines.join("\n")}
|
|
1011
|
-
}`;
|
|
1112
|
+
}` : `${head} {}`;
|
|
1113
|
+
}
|
|
1114
|
+
function isRuntimeClass(analysis, name) {
|
|
1115
|
+
return analysis.program.body.statements.some((statement) => {
|
|
1116
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
1117
|
+
return declaration.type === "ClassDeclaration" && declaration.name.name === name;
|
|
1118
|
+
});
|
|
1012
1119
|
}
|
|
1013
1120
|
function typeParameterText(analysis, parameter) {
|
|
1014
1121
|
return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
|
|
@@ -1050,7 +1157,27 @@ function fieldWithKey(table, key) {
|
|
|
1050
1157
|
const fields = table.fields;
|
|
1051
1158
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1052
1159
|
}
|
|
1053
|
-
function pretty(type) {
|
|
1160
|
+
function pretty(type, written) {
|
|
1161
|
+
const named = render(expandAliases(type, expansion.aliases, 0));
|
|
1162
|
+
const shorthand = written !== void 0 && written !== named ? written : void 0;
|
|
1163
|
+
const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
|
|
1164
|
+
if (depth < 0) return shorthand;
|
|
1165
|
+
return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
|
|
1166
|
+
}
|
|
1167
|
+
function asWritten(node) {
|
|
1168
|
+
if (!isSpanned(node) || !expansion.source) return void 0;
|
|
1169
|
+
const lines = expansion.source.split(/\r?\n/);
|
|
1170
|
+
const { line, column } = node;
|
|
1171
|
+
if (line.start < 1 || line.end > lines.length) return void 0;
|
|
1172
|
+
const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
|
|
1173
|
+
lines[line.start - 1].slice(column.start - 1),
|
|
1174
|
+
...lines.slice(line.start, line.end - 1),
|
|
1175
|
+
lines[line.end - 1].slice(0, column.end - 1)
|
|
1176
|
+
].join(" ");
|
|
1177
|
+
const folded = text.trim().replace(/\s+/g, " ");
|
|
1178
|
+
return folded.length ? folded : void 0;
|
|
1179
|
+
}
|
|
1180
|
+
function render(type) {
|
|
1054
1181
|
const flat = formatType3(type);
|
|
1055
1182
|
if (flat.length <= 80) return flat;
|
|
1056
1183
|
if (type.kind === "object") {
|
|
@@ -1069,11 +1196,11 @@ ${lines.join("\n")}
|
|
|
1069
1196
|
}
|
|
1070
1197
|
return flat;
|
|
1071
1198
|
}
|
|
1072
|
-
function bindingText(binding, type) {
|
|
1199
|
+
function bindingText(binding, type, written) {
|
|
1073
1200
|
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1074
|
-
return `function ${binding.name}${
|
|
1201
|
+
return `function ${binding.name}${pretty(type)}`;
|
|
1075
1202
|
}
|
|
1076
|
-
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1203
|
+
return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
|
|
1077
1204
|
}
|
|
1078
1205
|
function keyword(binding) {
|
|
1079
1206
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
@@ -1154,7 +1281,7 @@ import {
|
|
|
1154
1281
|
CompletionItemKind as CompletionItemKind3,
|
|
1155
1282
|
InsertTextFormat
|
|
1156
1283
|
} from "vscode-languageserver";
|
|
1157
|
-
import { formatType as formatType4, isClassType as
|
|
1284
|
+
import { formatType as formatType4, isClassType as isClassType3 } from "luaut-parser";
|
|
1158
1285
|
|
|
1159
1286
|
// src/features/autoImport.ts
|
|
1160
1287
|
import { readdirSync as readdirSync2 } from "fs";
|
|
@@ -1191,7 +1318,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
|
|
|
1191
1318
|
function serviceItems(analysis, taken) {
|
|
1192
1319
|
const services = analysis.types.aliases.get("Services");
|
|
1193
1320
|
if (!services || services.kind !== "object") return [];
|
|
1194
|
-
const
|
|
1321
|
+
const at2 = serviceInsertion(analysis.program.body.statements);
|
|
1195
1322
|
const items = [];
|
|
1196
1323
|
for (const name of services.properties.keys()) {
|
|
1197
1324
|
if (taken.has(name)) continue;
|
|
@@ -1202,8 +1329,8 @@ function serviceItems(analysis, taken) {
|
|
|
1202
1329
|
labelDetails: { description: "service" },
|
|
1203
1330
|
detail: line,
|
|
1204
1331
|
sortText: `5${name}`,
|
|
1205
|
-
additionalTextEdits: [{ range: { start:
|
|
1206
|
-
${
|
|
1332
|
+
additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
|
|
1333
|
+
${at2.gap}` }]
|
|
1207
1334
|
});
|
|
1208
1335
|
}
|
|
1209
1336
|
return items;
|
|
@@ -1215,24 +1342,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
|
|
|
1215
1342
|
if (existing) {
|
|
1216
1343
|
const last = existing.specifiers[existing.specifiers.length - 1];
|
|
1217
1344
|
if (last) {
|
|
1218
|
-
const
|
|
1219
|
-
return { range: { start:
|
|
1345
|
+
const at3 = endOf(last);
|
|
1346
|
+
return { range: { start: at3, end: at3 }, newText: `, ${name}` };
|
|
1220
1347
|
}
|
|
1221
1348
|
if (existing.defaultImport) {
|
|
1222
|
-
const
|
|
1223
|
-
return { range: { start:
|
|
1349
|
+
const at3 = endOf(existing.defaultImport);
|
|
1350
|
+
return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
|
|
1224
1351
|
}
|
|
1225
1352
|
}
|
|
1226
1353
|
const line = `import { ${name} } from "${specifier}"`;
|
|
1227
1354
|
const lastImport = imports[imports.length - 1];
|
|
1228
1355
|
if (lastImport) {
|
|
1229
|
-
const
|
|
1230
|
-
return { range: { start:
|
|
1356
|
+
const at3 = { line: lastImport.line.end, character: 0 };
|
|
1357
|
+
return { range: { start: at3, end: at3 }, newText: `${line}
|
|
1231
1358
|
` };
|
|
1232
1359
|
}
|
|
1233
1360
|
const first = statements[0];
|
|
1234
|
-
const
|
|
1235
|
-
return { range: { start:
|
|
1361
|
+
const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
|
|
1362
|
+
return { range: { start: at2, end: at2 }, newText: first ? `${line}
|
|
1236
1363
|
|
|
1237
1364
|
` : `${line}
|
|
1238
1365
|
` };
|
|
@@ -1325,12 +1452,12 @@ function completion(analyzer, document, position) {
|
|
|
1325
1452
|
const operator = memberOperator(source, start);
|
|
1326
1453
|
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
1327
1454
|
const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
|
|
1328
|
-
const
|
|
1455
|
+
const at2 = { line: position.line, character: position.character - (offset - start) };
|
|
1329
1456
|
let first;
|
|
1330
1457
|
for (const standIn of standIns) {
|
|
1331
1458
|
const patched = source.slice(0, start) + standIn + source.slice(end);
|
|
1332
1459
|
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
1333
|
-
const path = pathAt(analysis.program,
|
|
1460
|
+
const path = pathAt(analysis.program, at2, true);
|
|
1334
1461
|
const index = path.findLastIndex(
|
|
1335
1462
|
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
1336
1463
|
);
|
|
@@ -1346,8 +1473,8 @@ function completion(analyzer, document, position) {
|
|
|
1346
1473
|
if (inTypePosition(first.path)) {
|
|
1347
1474
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1348
1475
|
label: name,
|
|
1349
|
-
kind:
|
|
1350
|
-
detail:
|
|
1476
|
+
kind: isClassType3(type) ? CompletionItemKind3.Class : CompletionItemKind3.Interface,
|
|
1477
|
+
detail: isClassType3(type) ? "class" : "type"
|
|
1351
1478
|
}));
|
|
1352
1479
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1353
1480
|
label: name,
|
|
@@ -1367,7 +1494,7 @@ function completion(analyzer, document, position) {
|
|
|
1367
1494
|
for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
|
|
1368
1495
|
const current = analyzer.get(document);
|
|
1369
1496
|
return [
|
|
1370
|
-
...valueItems(first.analysis,
|
|
1497
|
+
...valueItems(first.analysis, at2, insideFunction(first.path)),
|
|
1371
1498
|
...contextKeywords(source.slice(0, start)),
|
|
1372
1499
|
...importItems(analyzer, current, false, taken),
|
|
1373
1500
|
...serviceItems(current, taken)
|
|
@@ -1441,8 +1568,8 @@ function objectKeyItems(analysis, path, after) {
|
|
|
1441
1568
|
}
|
|
1442
1569
|
function indexKeys(analysis, position, literal) {
|
|
1443
1570
|
const path = pathAt(analysis.program, position, false);
|
|
1444
|
-
const
|
|
1445
|
-
const parent =
|
|
1571
|
+
const at2 = path.indexOf(literal);
|
|
1572
|
+
const parent = at2 > 0 ? path[at2 - 1] : void 0;
|
|
1446
1573
|
if (!parent) return [];
|
|
1447
1574
|
let indexed;
|
|
1448
1575
|
if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
|
|
@@ -1534,14 +1661,18 @@ function isMethodOnly(type) {
|
|
|
1534
1661
|
return false;
|
|
1535
1662
|
}
|
|
1536
1663
|
}
|
|
1537
|
-
function
|
|
1664
|
+
function insideFunction(path) {
|
|
1665
|
+
return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
|
|
1666
|
+
}
|
|
1667
|
+
function valueItems(analysis, at2, inFunction) {
|
|
1538
1668
|
const items = [];
|
|
1539
1669
|
const seen = /* @__PURE__ */ new Set();
|
|
1540
1670
|
for (const binding of analysis.scopes.bindings.values()) {
|
|
1541
1671
|
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
1542
1672
|
if (binding.declaredBy === "type") continue;
|
|
1543
1673
|
const declaration = binding.declarationNode;
|
|
1544
|
-
|
|
1674
|
+
const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
|
|
1675
|
+
if (later && !inFunction && binding.declaredBy !== "function") continue;
|
|
1545
1676
|
seen.add(binding.name);
|
|
1546
1677
|
const type = analysis.types.bindingType.get(binding.id);
|
|
1547
1678
|
items.push({
|
|
@@ -1679,9 +1810,9 @@ function helpAt(analysis, position) {
|
|
|
1679
1810
|
function methodType(analysis, call) {
|
|
1680
1811
|
const object = call.object;
|
|
1681
1812
|
const method = call.method;
|
|
1682
|
-
const
|
|
1683
|
-
if (!
|
|
1684
|
-
return memberType(
|
|
1813
|
+
const objectType2 = analysis.types.typeOf.get(object);
|
|
1814
|
+
if (!objectType2) return void 0;
|
|
1815
|
+
return memberType(objectType2, method.name, analysis);
|
|
1685
1816
|
}
|
|
1686
1817
|
function memberType(type, name, analysis) {
|
|
1687
1818
|
if (type.kind === "object") return type.properties.get(name)?.type;
|
|
@@ -1733,6 +1864,15 @@ function documentSymbols(analysis) {
|
|
|
1733
1864
|
}
|
|
1734
1865
|
break;
|
|
1735
1866
|
}
|
|
1867
|
+
case "ClassDeclaration": {
|
|
1868
|
+
const declaration = node;
|
|
1869
|
+
const superclass = declaration.superclass?.name;
|
|
1870
|
+
out.push({
|
|
1871
|
+
...symbol(declaration.name.name, SymbolKind.Class, node, superclass && `extends ${superclass}`),
|
|
1872
|
+
children: declaration.members.map((member) => classMember(analysis, member))
|
|
1873
|
+
});
|
|
1874
|
+
break;
|
|
1875
|
+
}
|
|
1736
1876
|
case "DeclareClassStatement": {
|
|
1737
1877
|
const name = node.name.name;
|
|
1738
1878
|
const superclass = node.superclass?.base;
|
|
@@ -1750,6 +1890,23 @@ function documentSymbols(analysis) {
|
|
|
1750
1890
|
});
|
|
1751
1891
|
return out;
|
|
1752
1892
|
}
|
|
1893
|
+
function classMember(analysis, member) {
|
|
1894
|
+
switch (member.type) {
|
|
1895
|
+
case "ClassConstructor":
|
|
1896
|
+
return symbol("constructor", SymbolKind.Constructor, member);
|
|
1897
|
+
case "ClassField":
|
|
1898
|
+
return symbol(
|
|
1899
|
+
member.name.name,
|
|
1900
|
+
member.isStatic ? SymbolKind.Constant : SymbolKind.Field,
|
|
1901
|
+
member,
|
|
1902
|
+
member.typeAnnotation ? void 0 : detailOf(analysis, member)
|
|
1903
|
+
);
|
|
1904
|
+
case "ClassAccessor":
|
|
1905
|
+
return symbol(member.name.name, SymbolKind.Property, member, member.kind);
|
|
1906
|
+
case "ClassMethod":
|
|
1907
|
+
return symbol(member.name.name, SymbolKind.Method, member, member.isStatic ? "static" : void 0);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1753
1910
|
function functionName(node) {
|
|
1754
1911
|
const named = node;
|
|
1755
1912
|
if (typeof named.name === "string") return named.name;
|
|
@@ -1776,7 +1933,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1776
1933
|
}
|
|
1777
1934
|
|
|
1778
1935
|
// src/features/semanticTokens.ts
|
|
1779
|
-
import { tokenize, isClassType as
|
|
1936
|
+
import { tokenize, isClassType as isClassType4, unknownType } from "luaut-parser";
|
|
1780
1937
|
var TOKEN_TYPES = [
|
|
1781
1938
|
"namespace",
|
|
1782
1939
|
"type",
|
|
@@ -1806,15 +1963,17 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
1806
1963
|
"asserts",
|
|
1807
1964
|
"satisfies",
|
|
1808
1965
|
"typeof",
|
|
1809
|
-
"default"
|
|
1966
|
+
"default",
|
|
1967
|
+
"new",
|
|
1968
|
+
"super"
|
|
1810
1969
|
]);
|
|
1811
1970
|
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
|
|
1812
1971
|
var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
1813
1972
|
function semanticTokens(analysis) {
|
|
1814
1973
|
const entries = /* @__PURE__ */ new Map();
|
|
1815
|
-
const add = (
|
|
1816
|
-
const line =
|
|
1817
|
-
const character =
|
|
1974
|
+
const add = (at2, length, type, modifiers = []) => {
|
|
1975
|
+
const line = at2.line.start - 1;
|
|
1976
|
+
const character = at2.column.start - 1;
|
|
1818
1977
|
const key = `${line}:${character}`;
|
|
1819
1978
|
if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
|
|
1820
1979
|
};
|
|
@@ -1855,6 +2014,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1855
2014
|
add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
|
|
1856
2015
|
return;
|
|
1857
2016
|
}
|
|
2017
|
+
// A class body's own words. `get`, `set`, `static` and `constructor`
|
|
2018
|
+
// are ordinary names anywhere else, so they are coloured from the
|
|
2019
|
+
// member they open rather than wherever they are written.
|
|
2020
|
+
case "ClassMethod":
|
|
2021
|
+
case "ClassField":
|
|
2022
|
+
case "ClassAccessor":
|
|
2023
|
+
case "ClassConstructor": {
|
|
2024
|
+
const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
|
|
2025
|
+
const words = firstTokensWithin(identifiers, node, 2);
|
|
2026
|
+
let index = 0;
|
|
2027
|
+
if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
|
|
2028
|
+
add(words[index], "static".length, "keyword");
|
|
2029
|
+
index++;
|
|
2030
|
+
}
|
|
2031
|
+
if (opener && words[index] && wordOf(words[index]) === opener) {
|
|
2032
|
+
add(words[index], opener.length, "keyword");
|
|
2033
|
+
}
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
1858
2036
|
case "TypeReference": {
|
|
1859
2037
|
const base = node.base;
|
|
1860
2038
|
const namespace = node.namespace;
|
|
@@ -1864,7 +2042,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1864
2042
|
if (!baseToken) return;
|
|
1865
2043
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1866
2044
|
add(baseToken, base.length, "typeParameter");
|
|
1867
|
-
} else if (
|
|
2045
|
+
} else if (isClassType4(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? unknownType)) {
|
|
1868
2046
|
add(baseToken, base.length, "class");
|
|
1869
2047
|
} else {
|
|
1870
2048
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
@@ -1873,6 +2051,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1873
2051
|
}
|
|
1874
2052
|
}
|
|
1875
2053
|
}
|
|
2054
|
+
function wordOf(token) {
|
|
2055
|
+
const value = token.value;
|
|
2056
|
+
return typeof value === "string" ? value : void 0;
|
|
2057
|
+
}
|
|
1876
2058
|
function identifier(analysis, node, parent, add) {
|
|
1877
2059
|
const name = node.name;
|
|
1878
2060
|
const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
|
|
@@ -1896,6 +2078,19 @@ function identifier(analysis, node, parent, add) {
|
|
|
1896
2078
|
case "DeclareClassStatement":
|
|
1897
2079
|
if (parent.name === node) return as("class", ["declaration"]);
|
|
1898
2080
|
break;
|
|
2081
|
+
case "ClassDeclaration":
|
|
2082
|
+
if (parent.name === node) return as("class", ["declaration"]);
|
|
2083
|
+
if (parent.superclass === node) return as("class");
|
|
2084
|
+
break;
|
|
2085
|
+
case "ClassMethod":
|
|
2086
|
+
if (parent.name === node) return as("method", ["declaration"]);
|
|
2087
|
+
break;
|
|
2088
|
+
case "ClassField":
|
|
2089
|
+
if (parent.name === node) return as("property", ["declaration"]);
|
|
2090
|
+
break;
|
|
2091
|
+
case "ClassAccessor":
|
|
2092
|
+
if (parent.name === node) return as("property", ["declaration"]);
|
|
2093
|
+
break;
|
|
1899
2094
|
case "DeclareStatement":
|
|
1900
2095
|
if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
|
|
1901
2096
|
break;
|
|
@@ -2124,6 +2319,11 @@ function createServer(connection, options = {}) {
|
|
|
2124
2319
|
(d) => hover(analyzer.get(d), p.position),
|
|
2125
2320
|
null
|
|
2126
2321
|
));
|
|
2322
|
+
connection.onRequest("luaut/hover", (p) => withDocument(
|
|
2323
|
+
p.textDocument.uri,
|
|
2324
|
+
(d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
|
|
2325
|
+
null
|
|
2326
|
+
));
|
|
2127
2327
|
connection.onDefinition((p) => withDocument(
|
|
2128
2328
|
p.textDocument.uri,
|
|
2129
2329
|
(d) => {
|
|
@@ -2213,4 +2413,4 @@ export {
|
|
|
2213
2413
|
createServer,
|
|
2214
2414
|
startServer
|
|
2215
2415
|
};
|
|
2216
|
-
//# sourceMappingURL=chunk-
|
|
2416
|
+
//# sourceMappingURL=chunk-GHAR7NOH.js.map
|