luaut-language-server 4.0.0 → 5.1.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-7N7PVZTQ.js} +256 -53
- package/dist/chunk-7N7PVZTQ.js.map +1 -0
- package/dist/cli.cjs +259 -66
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +259 -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,104 @@ 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 {
|
|
820
|
+
isClassType,
|
|
821
|
+
arrayOf,
|
|
822
|
+
tuple,
|
|
823
|
+
objectType,
|
|
824
|
+
fn,
|
|
825
|
+
union as union2,
|
|
826
|
+
intersection,
|
|
827
|
+
aliasNameOf,
|
|
828
|
+
withoutAliasName
|
|
829
|
+
} from "luaut-parser";
|
|
830
|
+
function expandAliases(type, aliases, depth, seen = []) {
|
|
831
|
+
if (depth <= 0) return type;
|
|
832
|
+
const name = withheldName(type);
|
|
833
|
+
if (name !== void 0 && !seen.includes(name)) {
|
|
834
|
+
const opened = aliases.get(name) ?? type;
|
|
835
|
+
const inner = [...seen, name];
|
|
836
|
+
return children2(unnamed(opened), (t) => expandAliases(t, aliases, depth - 1, inner));
|
|
837
|
+
}
|
|
838
|
+
return children2(type, (t) => expandAliases(t, aliases, depth, seen));
|
|
839
|
+
}
|
|
840
|
+
function withheldName(type) {
|
|
841
|
+
if (type.kind === "genericRef") return type.typeArguments.length ? void 0 : type.name;
|
|
842
|
+
return aliasNameOf(type);
|
|
843
|
+
}
|
|
844
|
+
function unnamed(type) {
|
|
845
|
+
return withoutAliasName(type);
|
|
846
|
+
}
|
|
847
|
+
function children2(type, f) {
|
|
848
|
+
switch (type.kind) {
|
|
849
|
+
case "array":
|
|
850
|
+
return arrayOf(f(type.element));
|
|
851
|
+
case "tuple":
|
|
852
|
+
return tuple(type.elements.map(f), type.isPack);
|
|
853
|
+
case "union":
|
|
854
|
+
return union2(type.types.map(f));
|
|
855
|
+
case "intersection": {
|
|
856
|
+
const out = intersection(type.types.map(f));
|
|
857
|
+
return type.name && out.kind === "intersection" ? { ...out, name: type.name } : out;
|
|
858
|
+
}
|
|
859
|
+
case "object": {
|
|
860
|
+
if (isClassType(type)) return type;
|
|
861
|
+
const out = objectType(
|
|
862
|
+
[...type.properties].map(([name, property]) => [name, { ...property, type: f(property.type) }]),
|
|
863
|
+
type.indexer && { key: type.indexer.key, value: f(type.indexer.value) },
|
|
864
|
+
type.frozen
|
|
865
|
+
);
|
|
866
|
+
if (type.name) out.name = type.name;
|
|
867
|
+
return out;
|
|
868
|
+
}
|
|
869
|
+
case "function":
|
|
870
|
+
return fn(
|
|
871
|
+
type.params.map((p) => ({ ...p, type: f(p.type) })),
|
|
872
|
+
f(type.returns),
|
|
873
|
+
type.varargs && f(type.varargs),
|
|
874
|
+
type.typeParams,
|
|
875
|
+
type.predicate
|
|
876
|
+
);
|
|
877
|
+
default:
|
|
878
|
+
return type;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/features/hover.ts
|
|
883
|
+
function hover(analysis, position, depth = 0) {
|
|
817
884
|
const path = pathAt(analysis.program, position, true);
|
|
818
885
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
819
886
|
if (UNNAMED.has(path[i].type)) return null;
|
|
820
|
-
const text =
|
|
821
|
-
if (text)
|
|
887
|
+
const text = at(analysis, path, i, depth);
|
|
888
|
+
if (!text) continue;
|
|
889
|
+
const canExpand = at(analysis, path, i, depth + 1) !== text;
|
|
890
|
+
return {
|
|
891
|
+
contents: { kind: "markdown", value: code(text) },
|
|
892
|
+
range: toRange(path[i]),
|
|
893
|
+
depth,
|
|
894
|
+
canExpand
|
|
895
|
+
};
|
|
822
896
|
}
|
|
823
897
|
return null;
|
|
824
898
|
}
|
|
899
|
+
function at(analysis, path, index, depth) {
|
|
900
|
+
const previous = expansion;
|
|
901
|
+
expansion = { depth, aliases: analysis.types.aliases, source: analysis.source };
|
|
902
|
+
try {
|
|
903
|
+
return describe(analysis, path, index);
|
|
904
|
+
} finally {
|
|
905
|
+
expansion = previous;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
var expansion = {
|
|
909
|
+
depth: 0,
|
|
910
|
+
aliases: /* @__PURE__ */ new Map(),
|
|
911
|
+
source: ""
|
|
912
|
+
};
|
|
825
913
|
var UNNAMED = /* @__PURE__ */ new Set([
|
|
826
914
|
"BinaryExpression",
|
|
827
915
|
"UnaryExpression",
|
|
@@ -854,8 +942,8 @@ function describe(analysis, path, index) {
|
|
|
854
942
|
case "TableExpression": {
|
|
855
943
|
const field = fieldWithKey(parent, node);
|
|
856
944
|
if (!field) break;
|
|
857
|
-
const
|
|
858
|
-
const property =
|
|
945
|
+
const objectType2 = types.typeOf.get(parent);
|
|
946
|
+
const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
|
|
859
947
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
860
948
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
861
949
|
}
|
|
@@ -903,6 +991,19 @@ function describe(analysis, path, index) {
|
|
|
903
991
|
case "DeclareClassStatement":
|
|
904
992
|
if (parent.name === node) return classText(analysis, name);
|
|
905
993
|
break;
|
|
994
|
+
case "ClassDeclaration":
|
|
995
|
+
if (parent.name === node) return classText(analysis, name);
|
|
996
|
+
break;
|
|
997
|
+
case "ClassField":
|
|
998
|
+
if (parent.name === node) {
|
|
999
|
+
const type2 = typeOfNode(parent.typeAnnotation) ?? types.typeOf.get(parent.init);
|
|
1000
|
+
const prefix = parent.isStatic ? "(static) " : "(field) ";
|
|
1001
|
+
return type2 ? `${prefix}${name}: ${pretty(type2)}` : `${prefix}${name}`;
|
|
1002
|
+
}
|
|
1003
|
+
break;
|
|
1004
|
+
case "ClassAccessor":
|
|
1005
|
+
if (parent.name === node) return `(${parent.kind === "get" ? "getter" : "setter"}) ${name}`;
|
|
1006
|
+
break;
|
|
906
1007
|
case "TableTypeProperty":
|
|
907
1008
|
if (parent.key === node) {
|
|
908
1009
|
const type2 = typeOfNode(parent.valueType);
|
|
@@ -953,7 +1054,7 @@ function describe(analysis, path, index) {
|
|
|
953
1054
|
case "TypedIdentifier": {
|
|
954
1055
|
const binding = bindingOfNode(analysis, node);
|
|
955
1056
|
const type2 = binding && types.bindingType.get(binding.id);
|
|
956
|
-
return type2 ? bindingText(binding, type2) : void 0;
|
|
1057
|
+
return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
|
|
957
1058
|
}
|
|
958
1059
|
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
959
1060
|
// parameter in scope.
|
|
@@ -967,7 +1068,7 @@ function describe(analysis, path, index) {
|
|
|
967
1068
|
if (!node.typeArguments.length) {
|
|
968
1069
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
969
1070
|
const alias = types.aliases.get(qualified);
|
|
970
|
-
if (alias &&
|
|
1071
|
+
if (alias && isClassType2(alias)) return classText(analysis, qualified);
|
|
971
1072
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
972
1073
|
}
|
|
973
1074
|
const type2 = typeOfNode(node);
|
|
@@ -999,16 +1100,25 @@ function declareText(analysis, statement) {
|
|
|
999
1100
|
}
|
|
1000
1101
|
function classText(analysis, name) {
|
|
1001
1102
|
const type = analysis.types.aliases.get(name);
|
|
1002
|
-
if (!type || !
|
|
1103
|
+
if (!type || !isClassType2(type)) return void 0;
|
|
1003
1104
|
const superclass = type.class.superclass;
|
|
1004
1105
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
1005
|
-
const own = [...type.properties].filter(([key, property]) =>
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1106
|
+
const own = [...type.properties].filter(([key, property]) => (
|
|
1107
|
+
// `ClassObject` is on every instance and says nothing about this one.
|
|
1108
|
+
key !== "ClassObject" && (inherited?.kind !== "object" || inherited.properties.get(key) !== property)
|
|
1109
|
+
));
|
|
1110
|
+
const written = isRuntimeClass(analysis, name);
|
|
1111
|
+
const head = `${written ? "" : "declare "}class ${name}${superclass ? ` extends ${superclass}` : ""}`;
|
|
1112
|
+
const lines = own.map(([key, property]) => ` ${property.readonly ? "readonly " : ""}${key}${property.optional ? "?" : ""}: ${formatType3(property.type)}${written ? "" : ","}`);
|
|
1113
|
+
return own.length ? `${head} {
|
|
1010
1114
|
${lines.join("\n")}
|
|
1011
|
-
}`;
|
|
1115
|
+
}` : `${head} {}`;
|
|
1116
|
+
}
|
|
1117
|
+
function isRuntimeClass(analysis, name) {
|
|
1118
|
+
return analysis.program.body.statements.some((statement) => {
|
|
1119
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
1120
|
+
return declaration.type === "ClassDeclaration" && declaration.name.name === name;
|
|
1121
|
+
});
|
|
1012
1122
|
}
|
|
1013
1123
|
function typeParameterText(analysis, parameter) {
|
|
1014
1124
|
return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
|
|
@@ -1050,7 +1160,27 @@ function fieldWithKey(table, key) {
|
|
|
1050
1160
|
const fields = table.fields;
|
|
1051
1161
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1052
1162
|
}
|
|
1053
|
-
function pretty(type) {
|
|
1163
|
+
function pretty(type, written) {
|
|
1164
|
+
const named = render(expandAliases(type, expansion.aliases, 0));
|
|
1165
|
+
const shorthand = written !== void 0 && written !== named ? written : void 0;
|
|
1166
|
+
const depth = shorthand === void 0 ? expansion.depth : expansion.depth - 1;
|
|
1167
|
+
if (depth < 0) return shorthand;
|
|
1168
|
+
return depth === 0 ? named : render(expandAliases(type, expansion.aliases, depth));
|
|
1169
|
+
}
|
|
1170
|
+
function asWritten(node) {
|
|
1171
|
+
if (!isSpanned(node) || !expansion.source) return void 0;
|
|
1172
|
+
const lines = expansion.source.split(/\r?\n/);
|
|
1173
|
+
const { line, column } = node;
|
|
1174
|
+
if (line.start < 1 || line.end > lines.length) return void 0;
|
|
1175
|
+
const text = line.start === line.end ? lines[line.start - 1].slice(column.start - 1, column.end - 1) : [
|
|
1176
|
+
lines[line.start - 1].slice(column.start - 1),
|
|
1177
|
+
...lines.slice(line.start, line.end - 1),
|
|
1178
|
+
lines[line.end - 1].slice(0, column.end - 1)
|
|
1179
|
+
].join(" ");
|
|
1180
|
+
const folded = text.trim().replace(/\s+/g, " ");
|
|
1181
|
+
return folded.length ? folded : void 0;
|
|
1182
|
+
}
|
|
1183
|
+
function render(type) {
|
|
1054
1184
|
const flat = formatType3(type);
|
|
1055
1185
|
if (flat.length <= 80) return flat;
|
|
1056
1186
|
if (type.kind === "object") {
|
|
@@ -1069,11 +1199,11 @@ ${lines.join("\n")}
|
|
|
1069
1199
|
}
|
|
1070
1200
|
return flat;
|
|
1071
1201
|
}
|
|
1072
|
-
function bindingText(binding, type) {
|
|
1202
|
+
function bindingText(binding, type, written) {
|
|
1073
1203
|
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1074
|
-
return `function ${binding.name}${
|
|
1204
|
+
return `function ${binding.name}${pretty(type)}`;
|
|
1075
1205
|
}
|
|
1076
|
-
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1206
|
+
return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
|
|
1077
1207
|
}
|
|
1078
1208
|
function keyword(binding) {
|
|
1079
1209
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
@@ -1154,7 +1284,7 @@ import {
|
|
|
1154
1284
|
CompletionItemKind as CompletionItemKind3,
|
|
1155
1285
|
InsertTextFormat
|
|
1156
1286
|
} from "vscode-languageserver";
|
|
1157
|
-
import { formatType as formatType4, isClassType as
|
|
1287
|
+
import { formatType as formatType4, isClassType as isClassType3 } from "luaut-parser";
|
|
1158
1288
|
|
|
1159
1289
|
// src/features/autoImport.ts
|
|
1160
1290
|
import { readdirSync as readdirSync2 } from "fs";
|
|
@@ -1191,7 +1321,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
|
|
|
1191
1321
|
function serviceItems(analysis, taken) {
|
|
1192
1322
|
const services = analysis.types.aliases.get("Services");
|
|
1193
1323
|
if (!services || services.kind !== "object") return [];
|
|
1194
|
-
const
|
|
1324
|
+
const at2 = serviceInsertion(analysis.program.body.statements);
|
|
1195
1325
|
const items = [];
|
|
1196
1326
|
for (const name of services.properties.keys()) {
|
|
1197
1327
|
if (taken.has(name)) continue;
|
|
@@ -1202,8 +1332,8 @@ function serviceItems(analysis, taken) {
|
|
|
1202
1332
|
labelDetails: { description: "service" },
|
|
1203
1333
|
detail: line,
|
|
1204
1334
|
sortText: `5${name}`,
|
|
1205
|
-
additionalTextEdits: [{ range: { start:
|
|
1206
|
-
${
|
|
1335
|
+
additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
|
|
1336
|
+
${at2.gap}` }]
|
|
1207
1337
|
});
|
|
1208
1338
|
}
|
|
1209
1339
|
return items;
|
|
@@ -1215,24 +1345,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
|
|
|
1215
1345
|
if (existing) {
|
|
1216
1346
|
const last = existing.specifiers[existing.specifiers.length - 1];
|
|
1217
1347
|
if (last) {
|
|
1218
|
-
const
|
|
1219
|
-
return { range: { start:
|
|
1348
|
+
const at3 = endOf(last);
|
|
1349
|
+
return { range: { start: at3, end: at3 }, newText: `, ${name}` };
|
|
1220
1350
|
}
|
|
1221
1351
|
if (existing.defaultImport) {
|
|
1222
|
-
const
|
|
1223
|
-
return { range: { start:
|
|
1352
|
+
const at3 = endOf(existing.defaultImport);
|
|
1353
|
+
return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
|
|
1224
1354
|
}
|
|
1225
1355
|
}
|
|
1226
1356
|
const line = `import { ${name} } from "${specifier}"`;
|
|
1227
1357
|
const lastImport = imports[imports.length - 1];
|
|
1228
1358
|
if (lastImport) {
|
|
1229
|
-
const
|
|
1230
|
-
return { range: { start:
|
|
1359
|
+
const at3 = { line: lastImport.line.end, character: 0 };
|
|
1360
|
+
return { range: { start: at3, end: at3 }, newText: `${line}
|
|
1231
1361
|
` };
|
|
1232
1362
|
}
|
|
1233
1363
|
const first = statements[0];
|
|
1234
|
-
const
|
|
1235
|
-
return { range: { start:
|
|
1364
|
+
const at2 = { line: first ? first.line.start - 1 : 0, character: 0 };
|
|
1365
|
+
return { range: { start: at2, end: at2 }, newText: first ? `${line}
|
|
1236
1366
|
|
|
1237
1367
|
` : `${line}
|
|
1238
1368
|
` };
|
|
@@ -1325,12 +1455,12 @@ function completion(analyzer, document, position) {
|
|
|
1325
1455
|
const operator = memberOperator(source, start);
|
|
1326
1456
|
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
1327
1457
|
const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
|
|
1328
|
-
const
|
|
1458
|
+
const at2 = { line: position.line, character: position.character - (offset - start) };
|
|
1329
1459
|
let first;
|
|
1330
1460
|
for (const standIn of standIns) {
|
|
1331
1461
|
const patched = source.slice(0, start) + standIn + source.slice(end);
|
|
1332
1462
|
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
1333
|
-
const path = pathAt(analysis.program,
|
|
1463
|
+
const path = pathAt(analysis.program, at2, true);
|
|
1334
1464
|
const index = path.findLastIndex(
|
|
1335
1465
|
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
1336
1466
|
);
|
|
@@ -1346,8 +1476,8 @@ function completion(analyzer, document, position) {
|
|
|
1346
1476
|
if (inTypePosition(first.path)) {
|
|
1347
1477
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1348
1478
|
label: name,
|
|
1349
|
-
kind:
|
|
1350
|
-
detail:
|
|
1479
|
+
kind: isClassType3(type) ? CompletionItemKind3.Class : CompletionItemKind3.Interface,
|
|
1480
|
+
detail: isClassType3(type) ? "class" : "type"
|
|
1351
1481
|
}));
|
|
1352
1482
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1353
1483
|
label: name,
|
|
@@ -1367,7 +1497,7 @@ function completion(analyzer, document, position) {
|
|
|
1367
1497
|
for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
|
|
1368
1498
|
const current = analyzer.get(document);
|
|
1369
1499
|
return [
|
|
1370
|
-
...valueItems(first.analysis,
|
|
1500
|
+
...valueItems(first.analysis, at2, insideFunction(first.path)),
|
|
1371
1501
|
...contextKeywords(source.slice(0, start)),
|
|
1372
1502
|
...importItems(analyzer, current, false, taken),
|
|
1373
1503
|
...serviceItems(current, taken)
|
|
@@ -1441,8 +1571,8 @@ function objectKeyItems(analysis, path, after) {
|
|
|
1441
1571
|
}
|
|
1442
1572
|
function indexKeys(analysis, position, literal) {
|
|
1443
1573
|
const path = pathAt(analysis.program, position, false);
|
|
1444
|
-
const
|
|
1445
|
-
const parent =
|
|
1574
|
+
const at2 = path.indexOf(literal);
|
|
1575
|
+
const parent = at2 > 0 ? path[at2 - 1] : void 0;
|
|
1446
1576
|
if (!parent) return [];
|
|
1447
1577
|
let indexed;
|
|
1448
1578
|
if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
|
|
@@ -1534,14 +1664,18 @@ function isMethodOnly(type) {
|
|
|
1534
1664
|
return false;
|
|
1535
1665
|
}
|
|
1536
1666
|
}
|
|
1537
|
-
function
|
|
1667
|
+
function insideFunction(path) {
|
|
1668
|
+
return path.some((n) => n.type === "FunctionExpression" || n.type === "FunctionDeclaration" || n.type === "FunctionDeclarationStatement" || n.type === "FunctionBody");
|
|
1669
|
+
}
|
|
1670
|
+
function valueItems(analysis, at2, inFunction) {
|
|
1538
1671
|
const items = [];
|
|
1539
1672
|
const seen = /* @__PURE__ */ new Set();
|
|
1540
1673
|
for (const binding of analysis.scopes.bindings.values()) {
|
|
1541
1674
|
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
1542
1675
|
if (binding.declaredBy === "type") continue;
|
|
1543
1676
|
const declaration = binding.declarationNode;
|
|
1544
|
-
|
|
1677
|
+
const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
|
|
1678
|
+
if (later && !inFunction && binding.declaredBy !== "function") continue;
|
|
1545
1679
|
seen.add(binding.name);
|
|
1546
1680
|
const type = analysis.types.bindingType.get(binding.id);
|
|
1547
1681
|
items.push({
|
|
@@ -1679,9 +1813,9 @@ function helpAt(analysis, position) {
|
|
|
1679
1813
|
function methodType(analysis, call) {
|
|
1680
1814
|
const object = call.object;
|
|
1681
1815
|
const method = call.method;
|
|
1682
|
-
const
|
|
1683
|
-
if (!
|
|
1684
|
-
return memberType(
|
|
1816
|
+
const objectType2 = analysis.types.typeOf.get(object);
|
|
1817
|
+
if (!objectType2) return void 0;
|
|
1818
|
+
return memberType(objectType2, method.name, analysis);
|
|
1685
1819
|
}
|
|
1686
1820
|
function memberType(type, name, analysis) {
|
|
1687
1821
|
if (type.kind === "object") return type.properties.get(name)?.type;
|
|
@@ -1733,6 +1867,15 @@ function documentSymbols(analysis) {
|
|
|
1733
1867
|
}
|
|
1734
1868
|
break;
|
|
1735
1869
|
}
|
|
1870
|
+
case "ClassDeclaration": {
|
|
1871
|
+
const declaration = node;
|
|
1872
|
+
const superclass = declaration.superclass?.name;
|
|
1873
|
+
out.push({
|
|
1874
|
+
...symbol(declaration.name.name, SymbolKind.Class, node, superclass && `extends ${superclass}`),
|
|
1875
|
+
children: declaration.members.map((member) => classMember(analysis, member))
|
|
1876
|
+
});
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1736
1879
|
case "DeclareClassStatement": {
|
|
1737
1880
|
const name = node.name.name;
|
|
1738
1881
|
const superclass = node.superclass?.base;
|
|
@@ -1750,6 +1893,23 @@ function documentSymbols(analysis) {
|
|
|
1750
1893
|
});
|
|
1751
1894
|
return out;
|
|
1752
1895
|
}
|
|
1896
|
+
function classMember(analysis, member) {
|
|
1897
|
+
switch (member.type) {
|
|
1898
|
+
case "ClassConstructor":
|
|
1899
|
+
return symbol("constructor", SymbolKind.Constructor, member);
|
|
1900
|
+
case "ClassField":
|
|
1901
|
+
return symbol(
|
|
1902
|
+
member.name.name,
|
|
1903
|
+
member.isStatic ? SymbolKind.Constant : SymbolKind.Field,
|
|
1904
|
+
member,
|
|
1905
|
+
member.typeAnnotation ? void 0 : detailOf(analysis, member)
|
|
1906
|
+
);
|
|
1907
|
+
case "ClassAccessor":
|
|
1908
|
+
return symbol(member.name.name, SymbolKind.Property, member, member.kind);
|
|
1909
|
+
case "ClassMethod":
|
|
1910
|
+
return symbol(member.name.name, SymbolKind.Method, member, member.isStatic ? "static" : void 0);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1753
1913
|
function functionName(node) {
|
|
1754
1914
|
const named = node;
|
|
1755
1915
|
if (typeof named.name === "string") return named.name;
|
|
@@ -1776,7 +1936,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1776
1936
|
}
|
|
1777
1937
|
|
|
1778
1938
|
// src/features/semanticTokens.ts
|
|
1779
|
-
import { tokenize, isClassType as
|
|
1939
|
+
import { tokenize, isClassType as isClassType4, unknownType } from "luaut-parser";
|
|
1780
1940
|
var TOKEN_TYPES = [
|
|
1781
1941
|
"namespace",
|
|
1782
1942
|
"type",
|
|
@@ -1806,15 +1966,17 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
1806
1966
|
"asserts",
|
|
1807
1967
|
"satisfies",
|
|
1808
1968
|
"typeof",
|
|
1809
|
-
"default"
|
|
1969
|
+
"default",
|
|
1970
|
+
"new",
|
|
1971
|
+
"super"
|
|
1810
1972
|
]);
|
|
1811
1973
|
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
|
|
1812
1974
|
var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
1813
1975
|
function semanticTokens(analysis) {
|
|
1814
1976
|
const entries = /* @__PURE__ */ new Map();
|
|
1815
|
-
const add = (
|
|
1816
|
-
const line =
|
|
1817
|
-
const character =
|
|
1977
|
+
const add = (at2, length, type, modifiers = []) => {
|
|
1978
|
+
const line = at2.line.start - 1;
|
|
1979
|
+
const character = at2.column.start - 1;
|
|
1818
1980
|
const key = `${line}:${character}`;
|
|
1819
1981
|
if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
|
|
1820
1982
|
};
|
|
@@ -1855,6 +2017,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1855
2017
|
add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
|
|
1856
2018
|
return;
|
|
1857
2019
|
}
|
|
2020
|
+
// A class body's own words. `get`, `set`, `static` and `constructor`
|
|
2021
|
+
// are ordinary names anywhere else, so they are coloured from the
|
|
2022
|
+
// member they open rather than wherever they are written.
|
|
2023
|
+
case "ClassMethod":
|
|
2024
|
+
case "ClassField":
|
|
2025
|
+
case "ClassAccessor":
|
|
2026
|
+
case "ClassConstructor": {
|
|
2027
|
+
const opener = node.type === "ClassConstructor" ? "constructor" : node.type === "ClassAccessor" ? node.kind : void 0;
|
|
2028
|
+
const words = firstTokensWithin(identifiers, node, 2);
|
|
2029
|
+
let index = 0;
|
|
2030
|
+
if (node.isStatic === true && words[index] && wordOf(words[index]) === "static") {
|
|
2031
|
+
add(words[index], "static".length, "keyword");
|
|
2032
|
+
index++;
|
|
2033
|
+
}
|
|
2034
|
+
if (opener && words[index] && wordOf(words[index]) === opener) {
|
|
2035
|
+
add(words[index], opener.length, "keyword");
|
|
2036
|
+
}
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
1858
2039
|
case "TypeReference": {
|
|
1859
2040
|
const base = node.base;
|
|
1860
2041
|
const namespace = node.namespace;
|
|
@@ -1864,7 +2045,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1864
2045
|
if (!baseToken) return;
|
|
1865
2046
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1866
2047
|
add(baseToken, base.length, "typeParameter");
|
|
1867
|
-
} else if (
|
|
2048
|
+
} else if (isClassType4(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? unknownType)) {
|
|
1868
2049
|
add(baseToken, base.length, "class");
|
|
1869
2050
|
} else {
|
|
1870
2051
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
@@ -1873,6 +2054,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1873
2054
|
}
|
|
1874
2055
|
}
|
|
1875
2056
|
}
|
|
2057
|
+
function wordOf(token) {
|
|
2058
|
+
const value = token.value;
|
|
2059
|
+
return typeof value === "string" ? value : void 0;
|
|
2060
|
+
}
|
|
1876
2061
|
function identifier(analysis, node, parent, add) {
|
|
1877
2062
|
const name = node.name;
|
|
1878
2063
|
const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
|
|
@@ -1896,6 +2081,19 @@ function identifier(analysis, node, parent, add) {
|
|
|
1896
2081
|
case "DeclareClassStatement":
|
|
1897
2082
|
if (parent.name === node) return as("class", ["declaration"]);
|
|
1898
2083
|
break;
|
|
2084
|
+
case "ClassDeclaration":
|
|
2085
|
+
if (parent.name === node) return as("class", ["declaration"]);
|
|
2086
|
+
if (parent.superclass === node) return as("class");
|
|
2087
|
+
break;
|
|
2088
|
+
case "ClassMethod":
|
|
2089
|
+
if (parent.name === node) return as("method", ["declaration"]);
|
|
2090
|
+
break;
|
|
2091
|
+
case "ClassField":
|
|
2092
|
+
if (parent.name === node) return as("property", ["declaration"]);
|
|
2093
|
+
break;
|
|
2094
|
+
case "ClassAccessor":
|
|
2095
|
+
if (parent.name === node) return as("property", ["declaration"]);
|
|
2096
|
+
break;
|
|
1899
2097
|
case "DeclareStatement":
|
|
1900
2098
|
if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
|
|
1901
2099
|
break;
|
|
@@ -2124,6 +2322,11 @@ function createServer(connection, options = {}) {
|
|
|
2124
2322
|
(d) => hover(analyzer.get(d), p.position),
|
|
2125
2323
|
null
|
|
2126
2324
|
));
|
|
2325
|
+
connection.onRequest("luaut/hover", (p) => withDocument(
|
|
2326
|
+
p.textDocument.uri,
|
|
2327
|
+
(d) => hover(analyzer.get(d), p.position, Math.max(0, p.depth ?? 0)),
|
|
2328
|
+
null
|
|
2329
|
+
));
|
|
2127
2330
|
connection.onDefinition((p) => withDocument(
|
|
2128
2331
|
p.textDocument.uri,
|
|
2129
2332
|
(d) => {
|
|
@@ -2213,4 +2416,4 @@ export {
|
|
|
2213
2416
|
createServer,
|
|
2214
2417
|
startServer
|
|
2215
2418
|
};
|
|
2216
|
-
//# sourceMappingURL=chunk-
|
|
2419
|
+
//# sourceMappingURL=chunk-7N7PVZTQ.js.map
|