@venn-lang/lsp 0.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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/bin/venn-lsp.d.mts +1 -0
  4. package/dist/bin/venn-lsp.mjs +17 -0
  5. package/dist/bin/venn-lsp.mjs.map +1 -0
  6. package/dist/index.d.mts +722 -0
  7. package/dist/index.d.mts.map +1 -0
  8. package/dist/index.mjs +2 -0
  9. package/dist/server-BET60xwK.mjs +5376 -0
  10. package/dist/server-BET60xwK.mjs.map +1 -0
  11. package/package.json +65 -0
  12. package/src/bin/venn-lsp.ts +17 -0
  13. package/src/catalog/build-catalog.ts +72 -0
  14. package/src/catalog/catalog.types.ts +44 -0
  15. package/src/catalog/index.ts +2 -0
  16. package/src/code-actions/anchor.ts +49 -0
  17. package/src/code-actions/code-actions.ts +139 -0
  18. package/src/code-actions/exporting-modules.ts +73 -0
  19. package/src/code-actions/index.ts +5 -0
  20. package/src/completion/argument-items.ts +81 -0
  21. package/src/completion/completion.ts +416 -0
  22. package/src/completion/completion.types.ts +38 -0
  23. package/src/completion/context.ts +144 -0
  24. package/src/completion/icons.ts +83 -0
  25. package/src/completion/index.ts +4 -0
  26. package/src/completion/items.ts +232 -0
  27. package/src/completion/member-items.ts +147 -0
  28. package/src/completion/module-paths.ts +63 -0
  29. package/src/completion/options-map-items.ts +47 -0
  30. package/src/completion/read-from.ts +77 -0
  31. package/src/completion/type-name-items.ts +62 -0
  32. package/src/deco/builtin-decos.ts +35 -0
  33. package/src/deco/builtin-docs.ts +26 -0
  34. package/src/deco/declared-deco.ts +56 -0
  35. package/src/deco/deco.types.ts +23 -0
  36. package/src/deco/decos-in-scope.ts +63 -0
  37. package/src/deco/imported-decos.ts +49 -0
  38. package/src/deco/index.ts +9 -0
  39. package/src/deco/kind-of.ts +15 -0
  40. package/src/deco/render-deco.ts +44 -0
  41. package/src/definition/definition.ts +93 -0
  42. package/src/definition/index.ts +1 -0
  43. package/src/docs/doc.types.ts +17 -0
  44. package/src/docs/index.ts +4 -0
  45. package/src/docs/parse-doc.ts +42 -0
  46. package/src/docs/read-doc.ts +45 -0
  47. package/src/docs/render-doc.ts +42 -0
  48. package/src/document/exported-names.ts +30 -0
  49. package/src/document/expression-path.ts +43 -0
  50. package/src/document/find-binding.ts +82 -0
  51. package/src/document/host-at.ts +61 -0
  52. package/src/document/imported-modules.ts +91 -0
  53. package/src/document/imported-names.ts +15 -0
  54. package/src/document/index.ts +19 -0
  55. package/src/document/interpolation-at.ts +134 -0
  56. package/src/document/names-in-scope.ts +126 -0
  57. package/src/document/resolve-fragment.ts +38 -0
  58. package/src/document/resolve-imported.ts +35 -0
  59. package/src/effects/index.ts +1 -0
  60. package/src/effects/waiting-fns.ts +83 -0
  61. package/src/env/env-vars.ts +88 -0
  62. package/src/env/env.types.ts +9 -0
  63. package/src/env/index.ts +3 -0
  64. package/src/env/render-env.ts +25 -0
  65. package/src/formatting/formatter.ts +68 -0
  66. package/src/formatting/index.ts +1 -0
  67. package/src/hover/hover.ts +322 -0
  68. package/src/hover/index.ts +1 -0
  69. package/src/hover/keywords.ts +168 -0
  70. package/src/hover/render-action.ts +94 -0
  71. package/src/hover/render-decl.ts +155 -0
  72. package/src/hover/render-imported.ts +49 -0
  73. package/src/hover/render-symbol.ts +108 -0
  74. package/src/hover/render-type-name.ts +78 -0
  75. package/src/hover/resolve-symbol.ts +77 -0
  76. package/src/hover/type-hover.ts +62 -0
  77. package/src/index.ts +38 -0
  78. package/src/markdown/index.ts +1 -0
  79. package/src/markdown/markdown.ts +38 -0
  80. package/src/references/document-highlight.ts +45 -0
  81. package/src/references/find-occurrences.ts +49 -0
  82. package/src/references/index.ts +13 -0
  83. package/src/references/name-property.ts +14 -0
  84. package/src/references/occurrence-range.ts +23 -0
  85. package/src/references/occurrences.ts +124 -0
  86. package/src/references/references.ts +38 -0
  87. package/src/references/symbol-at.ts +114 -0
  88. package/src/references/symbol.types.ts +38 -0
  89. package/src/rename/declares-deco.ts +15 -0
  90. package/src/rename/index.ts +1 -0
  91. package/src/rename/rename.ts +73 -0
  92. package/src/semantic/highlight-calls.ts +56 -0
  93. package/src/semantic/highlight-interpolation.ts +135 -0
  94. package/src/semantic/highlight-keywords.ts +115 -0
  95. package/src/semantic/highlight-literals.ts +38 -0
  96. package/src/semantic/highlight-module.ts +36 -0
  97. package/src/semantic/highlight-names.ts +97 -0
  98. package/src/semantic/highlight-paths.ts +80 -0
  99. package/src/semantic/highlight.types.ts +10 -0
  100. package/src/semantic/index.ts +1 -0
  101. package/src/semantic/semantic-tokens.ts +32 -0
  102. package/src/server/index.ts +1 -0
  103. package/src/server/start-server.ts +15 -0
  104. package/src/services/index.ts +2 -0
  105. package/src/services/lsp.types.ts +14 -0
  106. package/src/services/venn-lsp-module.ts +65 -0
  107. package/src/signature/action-call.ts +82 -0
  108. package/src/signature/bare-call.ts +55 -0
  109. package/src/signature/call-shape.ts +90 -0
  110. package/src/signature/call-shape.types.ts +34 -0
  111. package/src/signature/declared-shape.ts +52 -0
  112. package/src/signature/index.ts +11 -0
  113. package/src/signature/paren-call.ts +77 -0
  114. package/src/signature/render-shape.ts +92 -0
  115. package/src/signature/shape-at.ts +35 -0
  116. package/src/signature/signature-help.ts +98 -0
  117. package/src/symbols/document-symbols.ts +68 -0
  118. package/src/symbols/index.ts +1 -0
  119. package/src/testing/fixtures/alias-import.vn +16 -0
  120. package/src/testing/fixtures/shared/auth.vn +33 -0
  121. package/src/testing/fixtures/venn.toml +8 -0
  122. package/src/testing/lsp-fixture.ts +77 -0
  123. package/src/types/index.ts +3 -0
  124. package/src/types/type-service.ts +144 -0
  125. package/src/types/warm-types.ts +30 -0
  126. package/src/validation/check-validator.ts +111 -0
  127. package/src/validation/index.ts +1 -0
  128. package/src/workspace/import-resolver.ts +187 -0
  129. package/src/workspace/index.ts +1 -0
@@ -0,0 +1,111 @@
1
+ import { ALL_CAPABILITIES } from "@venn-lang/contracts";
2
+ import type { Document, Problem, VennAstType } from "@venn-lang/core";
3
+ import {
4
+ buildRegistry,
5
+ checkDocument,
6
+ checkImports,
7
+ collectFragments,
8
+ type Registry,
9
+ } from "@venn-lang/runtime";
10
+ import { allPlugins } from "@venn-lang/stdlib";
11
+ import {
12
+ AstUtils,
13
+ type LangiumDocument,
14
+ type LangiumDocuments,
15
+ type ValidationAcceptor,
16
+ } from "langium";
17
+ import type { Range } from "vscode-languageserver";
18
+ import { importedFragments, importedModules, type ModuleGraph } from "../document/index.js";
19
+ import { envNames } from "../env/index.js";
20
+ import type { VennServices } from "../services/lsp.types.js";
21
+ import type { TypeService } from "../types/index.js";
22
+ import type { ImportResolver } from "../workspace/index.js";
23
+
24
+ /** Wire the runtime's static check (VN2003/4/5) into the editor's diagnostics. */
25
+ export function registerVennChecks(services: VennServices): void {
26
+ const registry = buildRegistry({ plugins: allPlugins, caps: ALL_CAPABILITIES });
27
+ const imports = services.imports;
28
+ const types = services.types;
29
+ const documents = services.shared.workspace.LangiumDocuments;
30
+ services.validation.ValidationRegistry.register<VennAstType>({
31
+ Document: (document, accept) =>
32
+ report({ document, accept, registry, imports, types, documents }),
33
+ });
34
+ }
35
+
36
+ function report(args: {
37
+ document: Document;
38
+ accept: ValidationAcceptor;
39
+ registry: Registry;
40
+ imports: ImportResolver;
41
+ types: TypeService;
42
+ documents: LangiumDocuments;
43
+ }): void {
44
+ const langiumDocument = AstUtils.getDocument(args.document);
45
+ const uri = langiumDocument.uri.toString();
46
+ const graph = importedModules({
47
+ root: args.document,
48
+ uri: langiumDocument.uri,
49
+ documents: args.documents,
50
+ imports: args.imports,
51
+ });
52
+ const fragments = knownFragments({ document: args.document, uri, graph });
53
+ const problems = checkDocument({
54
+ document: args.document,
55
+ registry: args.registry,
56
+ fragments,
57
+ env: declaredEnv(args.imports, langiumDocument),
58
+ uri: langiumDocument.uri.toString(),
59
+ });
60
+ // Type errors come from the shared service, so a file is inferred once per
61
+ // change and hover reads the very same result.
62
+ const typed = args.types.of(langiumDocument).problems;
63
+ const imported = checkImports({ document: args.document, uri, graph });
64
+ for (const problem of [...problems, ...typed, ...imported]) emit(problem, args, langiumDocument);
65
+ }
66
+
67
+ /**
68
+ * The variables `venn.toml` declares, or undefined when there is no manifest.
69
+ * Without one, every `env.*` read would look undeclared.
70
+ */
71
+ function declaredEnv(
72
+ imports: ImportResolver,
73
+ document: LangiumDocument,
74
+ ): readonly string[] | undefined {
75
+ const sections = imports.env(document.uri);
76
+ return Object.keys(sections).length > 0 ? envNames(sections) : undefined;
77
+ }
78
+
79
+ function emit(
80
+ problem: Problem,
81
+ args: { document: Document; accept: ValidationAcceptor },
82
+ langiumDocument: LangiumDocument,
83
+ ): void {
84
+ args.accept("error", problem.title, {
85
+ node: args.document,
86
+ range: rangeOf(problem, langiumDocument),
87
+ code: problem.code,
88
+ });
89
+ }
90
+
91
+ /**
92
+ * A fragment is known if this file declares one or imported one.
93
+ *
94
+ * Only real fragments count: the neighbouring files say which imported names
95
+ * are fragments, so `run` cannot accept a `pub fn` by mistake.
96
+ */
97
+ function knownFragments(args: {
98
+ document: Document;
99
+ uri: string;
100
+ graph: ModuleGraph;
101
+ }): Set<string> {
102
+ return new Set([...collectFragments(args.document).keys(), ...importedFragments(args)]);
103
+ }
104
+
105
+ function rangeOf(problem: Problem, document: LangiumDocument): Range {
106
+ const text = document.textDocument;
107
+ return {
108
+ start: text.positionAt(problem.span.offset),
109
+ end: text.positionAt(problem.span.offset + problem.span.length),
110
+ };
111
+ }
@@ -0,0 +1 @@
1
+ export { registerVennChecks } from "./check-validator.js";
@@ -0,0 +1,187 @@
1
+ import { readFileSync } from "node:fs";
2
+ import {
3
+ createTomlManifest,
4
+ dotenvFiles,
5
+ parseDotenv,
6
+ resolveAlias,
7
+ tomlDocs,
8
+ } from "@venn-lang/contracts";
9
+ import type { TypeSpec } from "@venn-lang/types";
10
+ import { type URI, UriUtils } from "langium";
11
+
12
+ /**
13
+ * Reads the nearest `venn.toml` and answers the project-level questions that
14
+ * depend on it: where an import points, and how this project formats.
15
+ */
16
+ export interface ImportResolver {
17
+ resolve(spec: string, base: URI): URI;
18
+ /** The `[paths]` aliases visible from a file, and the folder each maps to. */
19
+ aliases(base: URI): Record<string, URI>;
20
+ /** The raw `[format]` table, for {@link formatOptionsFrom}. */
21
+ formatSettings(base: URI): Record<string, unknown>;
22
+ /** Every variable the project declares: `[env.*]` and the dotenv files. */
23
+ env(base: URI): Record<string, Record<string, string>>;
24
+ /** The comment written above each key in `venn.toml`, as its documentation. */
25
+ envDocs(base: URI): Record<string, string>;
26
+ /**
27
+ * What an installed package publishes, derived at install into `target/types/`.
28
+ *
29
+ * Read here rather than derived here: deriving means loading the TypeScript
30
+ * compiler and reading a package's whole declaration graph, which is a second
31
+ * of work and cannot happen on a keystroke. Nothing installed, or nothing
32
+ * derived yet, means an imported name is `dynamic`, which is the truth about
33
+ * it and not a failure.
34
+ */
35
+ packageTypes(base: URI, packages: readonly string[]): Map<string, Record<string, TypeSpec>>;
36
+ }
37
+
38
+ interface Manifested {
39
+ root: URI;
40
+ paths: Record<string, string>;
41
+ format: Record<string, unknown>;
42
+ env: Record<string, Record<string, string>>;
43
+ docs: Record<string, string>;
44
+ }
45
+
46
+ const MAX_DEPTH = 12;
47
+
48
+ /**
49
+ * Relative specifiers resolve against the importing file; `#alias/…` resolves
50
+ * through the nearest `venn.toml`, searched upwards and cached per directory.
51
+ */
52
+ export function createImportResolver(): ImportResolver {
53
+ const cache = new Map<string, Manifested | undefined>();
54
+ return {
55
+ resolve(spec, base) {
56
+ const from = UriUtils.dirname(base);
57
+ if (spec.startsWith(".")) return UriUtils.resolvePath(from, spec);
58
+ const found = findManifest(from, cache);
59
+ const alias = found && resolveAlias({ spec, paths: found.paths });
60
+ if (!found || !alias) return UriUtils.resolvePath(from, spec);
61
+ return UriUtils.resolvePath(found.root, alias.dir, alias.rest);
62
+ },
63
+ aliases(base) {
64
+ const found = findManifest(UriUtils.dirname(base), cache);
65
+ if (!found) return {};
66
+ return Object.fromEntries(
67
+ Object.entries(found.paths).map(([key, dir]) => [
68
+ key,
69
+ UriUtils.resolvePath(found.root, dir),
70
+ ]),
71
+ );
72
+ },
73
+ formatSettings(base) {
74
+ return findManifest(UriUtils.dirname(base), cache)?.format ?? {};
75
+ },
76
+ env(base) {
77
+ return findManifest(UriUtils.dirname(base), cache)?.env ?? {};
78
+ },
79
+ packageTypes(base, packages) {
80
+ const found = findManifest(UriUtils.dirname(base), cache);
81
+ const out = new Map<string, Record<string, TypeSpec>>();
82
+ if (!found) return out;
83
+ for (const name of packages) {
84
+ const at = UriUtils.resolvePath(found.root, "target", "types", `${fileName(name)}.json`);
85
+ const derived = readDerived(at);
86
+ if (derived) out.set(name, derived);
87
+ }
88
+ return out;
89
+ },
90
+ envDocs(base) {
91
+ return findManifest(UriUtils.dirname(base), cache)?.docs ?? {};
92
+ },
93
+ };
94
+ }
95
+
96
+ function findManifest(
97
+ dir: URI,
98
+ cache: Map<string, Manifested | undefined>,
99
+ ): Manifested | undefined {
100
+ const key = dir.toString();
101
+ if (cache.has(key)) return cache.get(key);
102
+ const found = search(dir);
103
+ cache.set(key, found);
104
+ return found;
105
+ }
106
+
107
+ function search(start: URI): Manifested | undefined {
108
+ let dir = start;
109
+ for (let depth = 0; depth < MAX_DEPTH; depth++) {
110
+ const manifest = read(UriUtils.resolvePath(dir, "venn.toml"));
111
+ if (manifest) return { root: dir, ...manifest, env: withDotenv(dir, manifest) };
112
+ const parent = UriUtils.dirname(dir);
113
+ if (parent.toString() === dir.toString()) return undefined;
114
+ dir = parent;
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ /**
120
+ * What each environment holds once the dotenv files are folded in.
121
+ *
122
+ * The editor has to show what a run would see, and a run reads `.env` as well
123
+ * as `venn.toml`. `dotenvFiles` is the same list the runner walks, so the two
124
+ * cannot disagree about where a value lives. Only the environment the process
125
+ * was started with is left out: that belongs to whoever launches the program,
126
+ * not to the file being edited.
127
+ */
128
+ function withDotenv(dir: URI, manifest: Manifested0): Record<string, Record<string, string>> {
129
+ const names = new Set([...Object.keys(manifest.env), "local"]);
130
+ const out: Record<string, Record<string, string>> = {};
131
+ for (const name of names) {
132
+ out[name] = { ...(manifest.env[name] ?? {}), ...readDotenv(dir, manifest.files, name) };
133
+ }
134
+ return out;
135
+ }
136
+
137
+ function readDotenv(dir: URI, configured: readonly string[], name: string): Record<string, string> {
138
+ const out: Record<string, string> = {};
139
+ for (const file of dotenvFiles({ configured, name })) {
140
+ const content = readText(UriUtils.resolvePath(dir, file));
141
+ if (content !== undefined) Object.assign(out, parseDotenv(content));
142
+ }
143
+ return out;
144
+ }
145
+
146
+ function readText(uri: URI): string | undefined {
147
+ try {
148
+ return readFileSync(uri.fsPath, "utf8");
149
+ } catch {
150
+ return undefined;
151
+ }
152
+ }
153
+
154
+ /** What {@link read} returns, before the dotenv files are folded into it. */
155
+ type Manifested0 = Omit<Manifested, "root"> & { files: readonly string[] };
156
+
157
+ function read(uri: URI): Manifested0 | undefined {
158
+ try {
159
+ const content = readFileSync(uri.fsPath, "utf8");
160
+ const manifest = createTomlManifest({ content }).load();
161
+ return {
162
+ paths: manifest.paths,
163
+ format: manifest.format as Record<string, unknown>,
164
+ env: manifest.env,
165
+ files: manifest.envFiles,
166
+ docs: tomlDocs(content),
167
+ };
168
+ } catch {
169
+ return undefined;
170
+ }
171
+ }
172
+
173
+ /** A scope holds a slash and a file name cannot: `@types/node` becomes `@types__node`. */
174
+ function fileName(name: string): string {
175
+ return name.replace("/", "__");
176
+ }
177
+
178
+ function readDerived(uri: URI): Record<string, TypeSpec> | undefined {
179
+ try {
180
+ const parsed = JSON.parse(readFileSync(uri.fsPath, "utf8")) as {
181
+ exports?: Record<string, TypeSpec>;
182
+ };
183
+ return parsed.exports;
184
+ } catch {
185
+ return undefined;
186
+ }
187
+ }
@@ -0,0 +1 @@
1
+ export { createImportResolver, type ImportResolver } from "./import-resolver.js";