luaut-language-server 3.1.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-SCZTSX3Y.js → chunk-GHAR7NOH.js} +276 -65
- package/dist/chunk-GHAR7NOH.js.map +1 -0
- package/dist/cli.cjs +288 -77
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +288 -77
- 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-SCZTSX3Y.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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/features/members.ts
|
|
2
|
-
import { formatType } from "luaut-parser";
|
|
2
|
+
import { formatType, substitute, union } from "luaut-parser";
|
|
3
3
|
function literalKeys(key, aliases) {
|
|
4
4
|
if (!key) return [];
|
|
5
5
|
const resolved = key.kind === "genericRef" ? aliases.get(key.name) : key;
|
|
@@ -48,13 +48,28 @@ function membersOf(type, aliases, seen = /* @__PURE__ */ new Set()) {
|
|
|
48
48
|
}
|
|
49
49
|
case "typeParam":
|
|
50
50
|
return membersOf(type.constraint, aliases, seen);
|
|
51
|
+
// An array and a string answer to the methods the language gives them
|
|
52
|
+
// — `names:filter(f)`, `text:trim()`. They are written in the parser's
|
|
53
|
+
// prelude as `ArrayMethods<T>` and `StringMethods`, so the element
|
|
54
|
+
// type goes in where `T` stands.
|
|
55
|
+
case "array":
|
|
56
|
+
case "tuple": {
|
|
57
|
+
const element = type.kind === "array" ? type.element : union(type.elements);
|
|
58
|
+
const methods = aliases.get("ArrayMethods");
|
|
59
|
+
return methods ? membersOf(substitute(methods, /* @__PURE__ */ new Map([["T", element]])), aliases, seen) : [];
|
|
60
|
+
}
|
|
61
|
+
case "primitive":
|
|
62
|
+
return type.name === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
|
|
63
|
+
case "literal":
|
|
64
|
+
return type.base === "string" ? membersOf(aliases.get("StringMethods"), aliases, seen) : [];
|
|
51
65
|
default:
|
|
52
66
|
return [];
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
function takesSelf(type) {
|
|
56
70
|
for (const signature of signaturesOf(type)) {
|
|
57
|
-
|
|
71
|
+
const first = signature.params[0]?.name;
|
|
72
|
+
if (first === "self" || first === "this") return true;
|
|
58
73
|
}
|
|
59
74
|
return false;
|
|
60
75
|
}
|
|
@@ -75,7 +90,7 @@ function signatureLabel(signature) {
|
|
|
75
90
|
});
|
|
76
91
|
const generics = signature.typeParams?.length ? `<${signature.typeParams.join(", ")}>` : "";
|
|
77
92
|
const varargs = signature.varargs ? [`...: ${formatType(signature.varargs)}`] : [];
|
|
78
|
-
const label = `${generics}(${[...parameters, ...varargs].join(", ")})
|
|
93
|
+
const label = `${generics}(${[...parameters, ...varargs].join(", ")}) => ${formatType(signature.returns)}`;
|
|
79
94
|
return { label, parameters };
|
|
80
95
|
}
|
|
81
96
|
|
|
@@ -683,7 +698,7 @@ function exportDeclaration(analyzer, module, name, seen = /* @__PURE__ */ new Se
|
|
|
683
698
|
break;
|
|
684
699
|
case "ExportStatement": {
|
|
685
700
|
const declaration = statement.declaration;
|
|
686
|
-
if (declaration.type === "FunctionDeclaration") {
|
|
701
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
|
|
687
702
|
if (declaration.name.name === name) return here(declaration.name);
|
|
688
703
|
} else {
|
|
689
704
|
for (const target of declaration.names) {
|
|
@@ -797,17 +812,101 @@ function diagnostics(analysis) {
|
|
|
797
812
|
// src/features/hover.ts
|
|
798
813
|
import {
|
|
799
814
|
formatType as formatType3,
|
|
800
|
-
isClassType
|
|
815
|
+
isClassType as isClassType2
|
|
801
816
|
} from "luaut-parser";
|
|
802
|
-
|
|
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) {
|
|
803
881
|
const path = pathAt(analysis.program, position, true);
|
|
804
882
|
for (let i = path.length - 1; i >= 0; i--) {
|
|
805
883
|
if (UNNAMED.has(path[i].type)) return null;
|
|
806
|
-
const text =
|
|
807
|
-
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
|
+
};
|
|
808
893
|
}
|
|
809
894
|
return null;
|
|
810
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
|
+
};
|
|
811
910
|
var UNNAMED = /* @__PURE__ */ new Set([
|
|
812
911
|
"BinaryExpression",
|
|
813
912
|
"UnaryExpression",
|
|
@@ -840,8 +939,8 @@ function describe(analysis, path, index) {
|
|
|
840
939
|
case "TableExpression": {
|
|
841
940
|
const field = fieldWithKey(parent, node);
|
|
842
941
|
if (!field) break;
|
|
843
|
-
const
|
|
844
|
-
const property =
|
|
942
|
+
const objectType2 = types.typeOf.get(parent);
|
|
943
|
+
const property = objectType2?.kind === "object" ? objectType2.properties.get(name) : void 0;
|
|
845
944
|
const type2 = property?.type ?? types.typeOf.get(field.value);
|
|
846
945
|
return type2 && `(property) ${name}: ${pretty(type2)}`;
|
|
847
946
|
}
|
|
@@ -889,6 +988,19 @@ function describe(analysis, path, index) {
|
|
|
889
988
|
case "DeclareClassStatement":
|
|
890
989
|
if (parent.name === node) return classText(analysis, name);
|
|
891
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;
|
|
892
1004
|
case "TableTypeProperty":
|
|
893
1005
|
if (parent.key === node) {
|
|
894
1006
|
const type2 = typeOfNode(parent.valueType);
|
|
@@ -939,7 +1051,7 @@ function describe(analysis, path, index) {
|
|
|
939
1051
|
case "TypedIdentifier": {
|
|
940
1052
|
const binding = bindingOfNode(analysis, node);
|
|
941
1053
|
const type2 = binding && types.bindingType.get(binding.id);
|
|
942
|
-
return type2 ? bindingText(binding, type2) : void 0;
|
|
1054
|
+
return type2 ? bindingText(binding, type2, asWritten(node.typeAnnotation)) : void 0;
|
|
943
1055
|
}
|
|
944
1056
|
// A type written by name: `number`, `Shape`, `Partial<User>`, or a type
|
|
945
1057
|
// parameter in scope.
|
|
@@ -953,7 +1065,7 @@ function describe(analysis, path, index) {
|
|
|
953
1065
|
if (!node.typeArguments.length) {
|
|
954
1066
|
const qualified = node.namespace ? `${node.namespace}.${base}` : base;
|
|
955
1067
|
const alias = types.aliases.get(qualified);
|
|
956
|
-
if (alias &&
|
|
1068
|
+
if (alias && isClassType2(alias)) return classText(analysis, qualified);
|
|
957
1069
|
if (alias) return `type ${qualified} = ${pretty(alias)}`;
|
|
958
1070
|
}
|
|
959
1071
|
const type2 = typeOfNode(node);
|
|
@@ -985,16 +1097,25 @@ function declareText(analysis, statement) {
|
|
|
985
1097
|
}
|
|
986
1098
|
function classText(analysis, name) {
|
|
987
1099
|
const type = analysis.types.aliases.get(name);
|
|
988
|
-
if (!type || !
|
|
1100
|
+
if (!type || !isClassType2(type)) return void 0;
|
|
989
1101
|
const superclass = type.class.superclass;
|
|
990
1102
|
const inherited = superclass ? analysis.types.aliases.get(superclass) : void 0;
|
|
991
|
-
const own = [...type.properties].filter(([key, property]) =>
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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} {
|
|
996
1111
|
${lines.join("\n")}
|
|
997
|
-
}`;
|
|
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
|
+
});
|
|
998
1119
|
}
|
|
999
1120
|
function typeParameterText(analysis, parameter) {
|
|
1000
1121
|
return `(type parameter) ${typeParameterSignature(analysis, parameter)}`;
|
|
@@ -1036,7 +1157,27 @@ function fieldWithKey(table, key) {
|
|
|
1036
1157
|
const fields = table.fields;
|
|
1037
1158
|
return fields.find((f) => f.type === "TableFieldNamed" && f.key === key);
|
|
1038
1159
|
}
|
|
1039
|
-
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) {
|
|
1040
1181
|
const flat = formatType3(type);
|
|
1041
1182
|
if (flat.length <= 80) return flat;
|
|
1042
1183
|
if (type.kind === "object") {
|
|
@@ -1055,11 +1196,11 @@ ${lines.join("\n")}
|
|
|
1055
1196
|
}
|
|
1056
1197
|
return flat;
|
|
1057
1198
|
}
|
|
1058
|
-
function bindingText(binding, type) {
|
|
1199
|
+
function bindingText(binding, type, written) {
|
|
1059
1200
|
if (binding.declaredBy === "function" && type.kind === "function") {
|
|
1060
|
-
return `function ${binding.name}${
|
|
1201
|
+
return `function ${binding.name}${pretty(type)}`;
|
|
1061
1202
|
}
|
|
1062
|
-
return `${keyword(binding)} ${binding.name}: ${pretty(type)}`;
|
|
1203
|
+
return `${keyword(binding)} ${binding.name}: ${pretty(type, written)}`;
|
|
1063
1204
|
}
|
|
1064
1205
|
function keyword(binding) {
|
|
1065
1206
|
if (binding.kind === "param" || binding.kind === "self") return "(parameter)";
|
|
@@ -1140,7 +1281,7 @@ import {
|
|
|
1140
1281
|
CompletionItemKind as CompletionItemKind3,
|
|
1141
1282
|
InsertTextFormat
|
|
1142
1283
|
} from "vscode-languageserver";
|
|
1143
|
-
import { formatType as formatType4, isClassType as
|
|
1284
|
+
import { formatType as formatType4, isClassType as isClassType3 } from "luaut-parser";
|
|
1144
1285
|
|
|
1145
1286
|
// src/features/autoImport.ts
|
|
1146
1287
|
import { readdirSync as readdirSync2 } from "fs";
|
|
@@ -1177,7 +1318,7 @@ function importItems(analyzer, analysis, typePosition, taken) {
|
|
|
1177
1318
|
function serviceItems(analysis, taken) {
|
|
1178
1319
|
const services = analysis.types.aliases.get("Services");
|
|
1179
1320
|
if (!services || services.kind !== "object") return [];
|
|
1180
|
-
const
|
|
1321
|
+
const at2 = serviceInsertion(analysis.program.body.statements);
|
|
1181
1322
|
const items = [];
|
|
1182
1323
|
for (const name of services.properties.keys()) {
|
|
1183
1324
|
if (taken.has(name)) continue;
|
|
@@ -1188,8 +1329,8 @@ function serviceItems(analysis, taken) {
|
|
|
1188
1329
|
labelDetails: { description: "service" },
|
|
1189
1330
|
detail: line,
|
|
1190
1331
|
sortText: `5${name}`,
|
|
1191
|
-
additionalTextEdits: [{ range: { start:
|
|
1192
|
-
${
|
|
1332
|
+
additionalTextEdits: [{ range: { start: at2.position, end: at2.position }, newText: `${line}
|
|
1333
|
+
${at2.gap}` }]
|
|
1193
1334
|
});
|
|
1194
1335
|
}
|
|
1195
1336
|
return items;
|
|
@@ -1201,24 +1342,24 @@ function importEdit(analyzer, analysis, file, name, specifier, typePosition) {
|
|
|
1201
1342
|
if (existing) {
|
|
1202
1343
|
const last = existing.specifiers[existing.specifiers.length - 1];
|
|
1203
1344
|
if (last) {
|
|
1204
|
-
const
|
|
1205
|
-
return { range: { start:
|
|
1345
|
+
const at3 = endOf(last);
|
|
1346
|
+
return { range: { start: at3, end: at3 }, newText: `, ${name}` };
|
|
1206
1347
|
}
|
|
1207
1348
|
if (existing.defaultImport) {
|
|
1208
|
-
const
|
|
1209
|
-
return { range: { start:
|
|
1349
|
+
const at3 = endOf(existing.defaultImport);
|
|
1350
|
+
return { range: { start: at3, end: at3 }, newText: `, { ${name} }` };
|
|
1210
1351
|
}
|
|
1211
1352
|
}
|
|
1212
1353
|
const line = `import { ${name} } from "${specifier}"`;
|
|
1213
1354
|
const lastImport = imports[imports.length - 1];
|
|
1214
1355
|
if (lastImport) {
|
|
1215
|
-
const
|
|
1216
|
-
return { range: { start:
|
|
1356
|
+
const at3 = { line: lastImport.line.end, character: 0 };
|
|
1357
|
+
return { range: { start: at3, end: at3 }, newText: `${line}
|
|
1217
1358
|
` };
|
|
1218
1359
|
}
|
|
1219
1360
|
const first = statements[0];
|
|
1220
|
-
const
|
|
1221
|
-
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}
|
|
1222
1363
|
|
|
1223
1364
|
` : `${line}
|
|
1224
1365
|
` };
|
|
@@ -1311,12 +1452,12 @@ function completion(analyzer, document, position) {
|
|
|
1311
1452
|
const operator = memberOperator(source, start);
|
|
1312
1453
|
const alreadyCalled = /^\s*\(/.test(source.slice(end));
|
|
1313
1454
|
const standIns = operator === ":" ? [alreadyCalled ? PLACEHOLDER : `${PLACEHOLDER}()`] : operator === "." && !alreadyCalled ? [PLACEHOLDER, `${PLACEHOLDER}()`] : [PLACEHOLDER];
|
|
1314
|
-
const
|
|
1455
|
+
const at2 = { line: position.line, character: position.character - (offset - start) };
|
|
1315
1456
|
let first;
|
|
1316
1457
|
for (const standIn of standIns) {
|
|
1317
1458
|
const patched = source.slice(0, start) + standIn + source.slice(end);
|
|
1318
1459
|
const analysis = analyzer.analyze(document.uri, -1, patched);
|
|
1319
|
-
const path = pathAt(analysis.program,
|
|
1460
|
+
const path = pathAt(analysis.program, at2, true);
|
|
1320
1461
|
const index = path.findLastIndex(
|
|
1321
1462
|
(n) => n.type === "Identifier" && n.name === PLACEHOLDER
|
|
1322
1463
|
);
|
|
@@ -1332,8 +1473,8 @@ function completion(analyzer, document, position) {
|
|
|
1332
1473
|
if (inTypePosition(first.path)) {
|
|
1333
1474
|
const named = [...first.analysis.types.aliases].map(([name, type]) => ({
|
|
1334
1475
|
label: name,
|
|
1335
|
-
kind:
|
|
1336
|
-
detail:
|
|
1476
|
+
kind: isClassType3(type) ? CompletionItemKind3.Class : CompletionItemKind3.Interface,
|
|
1477
|
+
detail: isClassType3(type) ? "class" : "type"
|
|
1337
1478
|
}));
|
|
1338
1479
|
const primitives = PRIMITIVES2.map((name) => ({
|
|
1339
1480
|
label: name,
|
|
@@ -1353,7 +1494,7 @@ function completion(analyzer, document, position) {
|
|
|
1353
1494
|
for (const binding of first.analysis.scopes.bindings.values()) taken.add(binding.name);
|
|
1354
1495
|
const current = analyzer.get(document);
|
|
1355
1496
|
return [
|
|
1356
|
-
...valueItems(first.analysis,
|
|
1497
|
+
...valueItems(first.analysis, at2, insideFunction(first.path)),
|
|
1357
1498
|
...contextKeywords(source.slice(0, start)),
|
|
1358
1499
|
...importItems(analyzer, current, false, taken),
|
|
1359
1500
|
...serviceItems(current, taken)
|
|
@@ -1427,8 +1568,8 @@ function objectKeyItems(analysis, path, after) {
|
|
|
1427
1568
|
}
|
|
1428
1569
|
function indexKeys(analysis, position, literal) {
|
|
1429
1570
|
const path = pathAt(analysis.program, position, false);
|
|
1430
|
-
const
|
|
1431
|
-
const parent =
|
|
1571
|
+
const at2 = path.indexOf(literal);
|
|
1572
|
+
const parent = at2 > 0 ? path[at2 - 1] : void 0;
|
|
1432
1573
|
if (!parent) return [];
|
|
1433
1574
|
let indexed;
|
|
1434
1575
|
if (parent.type === "IndexedAccessTypeNode" && parent.indexType === literal) {
|
|
@@ -1495,12 +1636,7 @@ function memberItems(analysis, access) {
|
|
|
1495
1636
|
const object = access.object;
|
|
1496
1637
|
const type = withoutNil(analysis.types.typeOf.get(object));
|
|
1497
1638
|
const colon = access.type === "MethodCallExpression";
|
|
1498
|
-
if (
|
|
1499
|
-
if (!colon) return [];
|
|
1500
|
-
const id = analysis.scopes.globalsByName.get("string");
|
|
1501
|
-
const library = id === void 0 ? void 0 : analysis.types.bindingType.get(id);
|
|
1502
|
-
return membersOf(library, analysis.types.aliases).filter((member) => signaturesOf(member.property.type).length > 0).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
1503
|
-
}
|
|
1639
|
+
if (isMethodOnly(type) && !colon) return [];
|
|
1504
1640
|
return membersOf(type, analysis.types.aliases).filter((member) => colon ? member.isMethod : true).map((member) => memberItem(member.name, member.property.type, member.property.readonly));
|
|
1505
1641
|
}
|
|
1506
1642
|
function withoutNil(type) {
|
|
@@ -1508,29 +1644,35 @@ function withoutNil(type) {
|
|
|
1508
1644
|
const kept = type.types.filter((t) => !(t.kind === "primitive" && t.name === "nil"));
|
|
1509
1645
|
return kept.length === 1 ? kept[0] : { ...type, types: kept };
|
|
1510
1646
|
}
|
|
1511
|
-
function
|
|
1647
|
+
function isMethodOnly(type) {
|
|
1512
1648
|
if (!type) return false;
|
|
1513
1649
|
switch (type.kind) {
|
|
1650
|
+
case "array":
|
|
1651
|
+
case "tuple":
|
|
1652
|
+
case "templateLiteral":
|
|
1653
|
+
return true;
|
|
1514
1654
|
case "primitive":
|
|
1515
1655
|
return type.name === "string";
|
|
1516
1656
|
case "literal":
|
|
1517
1657
|
return typeof type.value === "string";
|
|
1518
|
-
case "templateLiteral":
|
|
1519
|
-
return true;
|
|
1520
1658
|
case "union":
|
|
1521
|
-
return type.types.length > 0 && type.types.every(
|
|
1659
|
+
return type.types.length > 0 && type.types.every(isMethodOnly);
|
|
1522
1660
|
default:
|
|
1523
1661
|
return false;
|
|
1524
1662
|
}
|
|
1525
1663
|
}
|
|
1526
|
-
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) {
|
|
1527
1668
|
const items = [];
|
|
1528
1669
|
const seen = /* @__PURE__ */ new Set();
|
|
1529
1670
|
for (const binding of analysis.scopes.bindings.values()) {
|
|
1530
1671
|
if (binding.name === PLACEHOLDER || seen.has(binding.name)) continue;
|
|
1531
1672
|
if (binding.declaredBy === "type") continue;
|
|
1532
1673
|
const declaration = binding.declarationNode;
|
|
1533
|
-
|
|
1674
|
+
const later = declaration !== void 0 && declaration.line.start - 1 > at2.line;
|
|
1675
|
+
if (later && !inFunction && binding.declaredBy !== "function") continue;
|
|
1534
1676
|
seen.add(binding.name);
|
|
1535
1677
|
const type = analysis.types.bindingType.get(binding.id);
|
|
1536
1678
|
items.push({
|
|
@@ -1668,9 +1810,9 @@ function helpAt(analysis, position) {
|
|
|
1668
1810
|
function methodType(analysis, call) {
|
|
1669
1811
|
const object = call.object;
|
|
1670
1812
|
const method = call.method;
|
|
1671
|
-
const
|
|
1672
|
-
if (!
|
|
1673
|
-
return memberType(
|
|
1813
|
+
const objectType2 = analysis.types.typeOf.get(object);
|
|
1814
|
+
if (!objectType2) return void 0;
|
|
1815
|
+
return memberType(objectType2, method.name, analysis);
|
|
1674
1816
|
}
|
|
1675
1817
|
function memberType(type, name, analysis) {
|
|
1676
1818
|
if (type.kind === "object") return type.properties.get(name)?.type;
|
|
@@ -1722,6 +1864,15 @@ function documentSymbols(analysis) {
|
|
|
1722
1864
|
}
|
|
1723
1865
|
break;
|
|
1724
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
|
+
}
|
|
1725
1876
|
case "DeclareClassStatement": {
|
|
1726
1877
|
const name = node.name.name;
|
|
1727
1878
|
const superclass = node.superclass?.base;
|
|
@@ -1739,6 +1890,23 @@ function documentSymbols(analysis) {
|
|
|
1739
1890
|
});
|
|
1740
1891
|
return out;
|
|
1741
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
|
+
}
|
|
1742
1910
|
function functionName(node) {
|
|
1743
1911
|
const named = node;
|
|
1744
1912
|
if (typeof named.name === "string") return named.name;
|
|
@@ -1765,7 +1933,7 @@ function symbol(name, kind, node, detail) {
|
|
|
1765
1933
|
}
|
|
1766
1934
|
|
|
1767
1935
|
// src/features/semanticTokens.ts
|
|
1768
|
-
import { tokenize, isClassType as
|
|
1936
|
+
import { tokenize, isClassType as isClassType4, unknownType } from "luaut-parser";
|
|
1769
1937
|
var TOKEN_TYPES = [
|
|
1770
1938
|
"namespace",
|
|
1771
1939
|
"type",
|
|
@@ -1795,15 +1963,17 @@ var SOFT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
1795
1963
|
"asserts",
|
|
1796
1964
|
"satisfies",
|
|
1797
1965
|
"typeof",
|
|
1798
|
-
"default"
|
|
1966
|
+
"default",
|
|
1967
|
+
"new",
|
|
1968
|
+
"super"
|
|
1799
1969
|
]);
|
|
1800
1970
|
var CONTROL_KEYWORDS = /* @__PURE__ */ new Set(["default"]);
|
|
1801
1971
|
var PRIMITIVES3 = /* @__PURE__ */ new Set(["any", "unknown", "never", "nil", "boolean", "number", "string", "thread", "buffer"]);
|
|
1802
1972
|
function semanticTokens(analysis) {
|
|
1803
1973
|
const entries = /* @__PURE__ */ new Map();
|
|
1804
|
-
const add = (
|
|
1805
|
-
const line =
|
|
1806
|
-
const character =
|
|
1974
|
+
const add = (at2, length, type, modifiers = []) => {
|
|
1975
|
+
const line = at2.line.start - 1;
|
|
1976
|
+
const character = at2.column.start - 1;
|
|
1807
1977
|
const key = `${line}:${character}`;
|
|
1808
1978
|
if (!entries.has(key)) entries.set(key, { line, character, length, type, modifiers });
|
|
1809
1979
|
};
|
|
@@ -1844,6 +2014,25 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1844
2014
|
add(node, name.length, valueKind(analysis, binding), modifiersOf(binding, true));
|
|
1845
2015
|
return;
|
|
1846
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
|
+
}
|
|
1847
2036
|
case "TypeReference": {
|
|
1848
2037
|
const base = node.base;
|
|
1849
2038
|
const namespace = node.namespace;
|
|
@@ -1853,7 +2042,7 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1853
2042
|
if (!baseToken) return;
|
|
1854
2043
|
if (!namespace && typeParameterInScope2(ancestors, base)) {
|
|
1855
2044
|
add(baseToken, base.length, "typeParameter");
|
|
1856
|
-
} else if (
|
|
2045
|
+
} else if (isClassType4(analysis.types.aliases.get(namespace ? `${namespace}.${base}` : base) ?? unknownType)) {
|
|
1857
2046
|
add(baseToken, base.length, "class");
|
|
1858
2047
|
} else {
|
|
1859
2048
|
add(baseToken, base.length, "type", PRIMITIVES3.has(base) ? ["defaultLibrary"] : []);
|
|
@@ -1862,6 +2051,10 @@ function classify(analysis, spanned, ancestors, identifiers, add) {
|
|
|
1862
2051
|
}
|
|
1863
2052
|
}
|
|
1864
2053
|
}
|
|
2054
|
+
function wordOf(token) {
|
|
2055
|
+
const value = token.value;
|
|
2056
|
+
return typeof value === "string" ? value : void 0;
|
|
2057
|
+
}
|
|
1865
2058
|
function identifier(analysis, node, parent, add) {
|
|
1866
2059
|
const name = node.name;
|
|
1867
2060
|
const as = (type, modifiers = []) => add(node, name.length, type, modifiers);
|
|
@@ -1885,6 +2078,19 @@ function identifier(analysis, node, parent, add) {
|
|
|
1885
2078
|
case "DeclareClassStatement":
|
|
1886
2079
|
if (parent.name === node) return as("class", ["declaration"]);
|
|
1887
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;
|
|
1888
2094
|
case "DeclareStatement":
|
|
1889
2095
|
if (parent.id === node) return as(isFunction(typeOfNode(parent.valueType)) ? "function" : "variable", ["declaration"]);
|
|
1890
2096
|
break;
|
|
@@ -2094,7 +2300,7 @@ function createServer(connection, options = {}) {
|
|
|
2094
2300
|
severity: DiagnosticSeverity2.Information,
|
|
2095
2301
|
source: "luaut",
|
|
2096
2302
|
code: "no-config",
|
|
2097
|
-
message: 'No luaut.config.json applies to this file, so no types are loaded \u2014 not even `print`. Add one to this folder or a folder above
|
|
2303
|
+
message: 'No luaut.config.json applies to this file, so no types are loaded \u2014 not even `print`. Add one to this folder or a folder above: { "types": [], "paths": {}, "sourceMap": null }, listing in `types` the type libraries the project has installed.'
|
|
2098
2304
|
}];
|
|
2099
2305
|
};
|
|
2100
2306
|
documents.onDidOpen(publishAll);
|
|
@@ -2113,6 +2319,11 @@ function createServer(connection, options = {}) {
|
|
|
2113
2319
|
(d) => hover(analyzer.get(d), p.position),
|
|
2114
2320
|
null
|
|
2115
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
|
+
));
|
|
2116
2327
|
connection.onDefinition((p) => withDocument(
|
|
2117
2328
|
p.textDocument.uri,
|
|
2118
2329
|
(d) => {
|
|
@@ -2202,4 +2413,4 @@ export {
|
|
|
2202
2413
|
createServer,
|
|
2203
2414
|
startServer
|
|
2204
2415
|
};
|
|
2205
|
-
//# sourceMappingURL=chunk-
|
|
2416
|
+
//# sourceMappingURL=chunk-GHAR7NOH.js.map
|