@prisma-next/language-server 0.14.0-dev.47 → 0.14.0-dev.48

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.
@@ -1,60 +1,107 @@
1
- import type { SymbolTable } from '@prisma-next/psl-parser';
1
+ import { buildSymbolTable, type SymbolTable } from '@prisma-next/psl-parser';
2
2
  import type { DocumentAst, SourceFile } from '@prisma-next/psl-parser/syntax';
3
3
  import type { LspDiagnostic } from './diagnostic-mapping';
4
4
  import { computeDocumentDiagnostics } from './document-diagnostics';
5
5
  import type { PipelineInputs } from './pipeline';
6
6
  import type { SchemaInputSet } from './schema-inputs';
7
7
 
8
- export interface CachedDocument {
8
+ export interface DocumentArtifacts {
9
9
  readonly document: DocumentAst;
10
10
  readonly sourceFile: SourceFile;
11
- readonly text: string;
11
+ readonly diagnostics: readonly LspDiagnostic[];
12
12
  }
13
13
 
14
+ export interface ProjectArtifactsOptions {
15
+ readonly inputs: SchemaInputSet;
16
+ readonly controlStack: PipelineInputs;
17
+ readonly getText: (uri: string) => string | undefined;
18
+ }
19
+
20
+ /**
21
+ * Reads can never observe stale artifacts: the vscode-languageserver runtime
22
+ * dispatches messages in order and the server raises `documentChanged` /
23
+ * `documentClosed` synchronously against the already-updated text mirror, so
24
+ * every mutation that could affect a read lands before that read runs. A
25
+ * config reload replaces the store wholesale.
26
+ */
14
27
  export interface ProjectArtifacts {
15
- getDocument(uri: string): CachedDocument | undefined;
16
- getSymbolTable(): SymbolTable | undefined;
17
- update(
18
- uri: string,
19
- text: string,
20
- inputs: SchemaInputSet,
21
- controlStack: PipelineInputs,
22
- ): readonly LspDiagnostic[] | null;
23
- remove(uri: string): void;
28
+ /**
29
+ * `undefined` when the document is not open in the text mirror or is not
30
+ * one of the project's configured inputs.
31
+ */
32
+ document(uri: string): DocumentArtifacts | undefined;
33
+ symbolTable(): SymbolTable;
34
+ documentChanged(uri: string): void;
35
+ documentClosed(uri: string): void;
24
36
  }
25
37
 
26
- export function createProjectArtifacts(): ProjectArtifacts {
27
- const documents = new Map<string, CachedDocument>();
38
+ export function createProjectArtifacts(options: ProjectArtifactsOptions): ProjectArtifacts {
39
+ const { inputs, controlStack, getText } = options;
40
+ const documents = new Map<string, DocumentArtifacts>();
28
41
  let symbolTable: SymbolTable | undefined;
29
42
 
43
+ function drop(uri: string): void {
44
+ if (documents.delete(uri)) {
45
+ symbolTable = undefined;
46
+ }
47
+ }
48
+
49
+ function readDocument(uri: string): DocumentArtifacts | undefined {
50
+ const existing = documents.get(uri);
51
+ if (existing !== undefined) {
52
+ return existing;
53
+ }
54
+ const text = getText(uri);
55
+ if (text === undefined) {
56
+ return undefined;
57
+ }
58
+ const computed = computeDocumentDiagnostics(uri, text, inputs, controlStack);
59
+ if (computed === null) {
60
+ return undefined;
61
+ }
62
+ const artifacts: DocumentArtifacts = {
63
+ document: computed.document,
64
+ sourceFile: computed.sourceFile,
65
+ diagnostics: computed.diagnostics,
66
+ };
67
+ documents.set(uri, artifacts);
68
+ // Single-input by design: the project-wide symbolTable is rebuilt from the
69
+ // one open configured input; merging multiple inputs (and reading unopened
70
+ // ones from disk) is deferred cross-file work.
71
+ symbolTable = computed.symbolTable;
72
+ return artifacts;
73
+ }
74
+
30
75
  return {
31
- getDocument: (uri) => documents.get(uri),
32
- getSymbolTable: () => symbolTable,
33
- update: (uri, text, inputs, controlStack) => {
34
- const computed = computeDocumentDiagnostics(uri, text, inputs, controlStack);
35
- if (computed === null) {
36
- if (documents.delete(uri)) {
37
- symbolTable = undefined;
38
- }
39
- return null;
76
+ document: readDocument,
77
+ symbolTable: () => {
78
+ if (symbolTable !== undefined) {
79
+ return symbolTable;
40
80
  }
41
- documents.set(uri, {
42
- document: computed.document,
43
- sourceFile: computed.sourceFile,
44
- text,
45
- });
46
- // One symbol table per project. Single-input reality: it is (re)built from
47
- // the one open configured input on every edit. Merging several open inputs
48
- // into one project table — and reading unopened `inputs` from disk — is the
49
- // deferred cross-file work; `buildSymbolTable`'s single-document signature
50
- // is left untouched for it.
51
- symbolTable = computed.symbolTable;
52
- return computed.diagnostics;
53
- },
54
- remove: (uri) => {
55
- if (documents.delete(uri)) {
56
- symbolTable = undefined;
81
+ for (const uri of inputs.uris()) {
82
+ const artifacts = readDocument(uri);
83
+ if (artifacts === undefined) {
84
+ continue;
85
+ }
86
+ // A read that hits existing artifacts leaves the slot unset (the
87
+ // contributing input may have closed since); rebuild from the
88
+ // artifacts without reparsing.
89
+ symbolTable ??= buildSymbolTable({
90
+ document: artifacts.document,
91
+ sourceFile: artifacts.sourceFile,
92
+ scalarTypes: controlStack.scalarTypes,
93
+ pslBlockDescriptors: controlStack.pslBlockDescriptors,
94
+ }).table;
95
+ return symbolTable;
57
96
  }
97
+ // The server's lifecycle makes this unreachable: it drops a project
98
+ // once its last open input closes. Throwing loudly beats serving a
99
+ // fabricated empty symbolTable that would mask the broken invariant.
100
+ throw new Error(
101
+ 'invariant violated: project has no open configured input — the server must drop such projects',
102
+ );
58
103
  },
104
+ documentChanged: drop,
105
+ documentClosed: drop,
59
106
  };
60
107
  }
@@ -11,6 +11,7 @@ export interface SchemaInputConfig {
11
11
 
12
12
  export interface SchemaInputSet {
13
13
  includes(uri: string): boolean;
14
+ uris(): Iterable<string>;
14
15
  }
15
16
 
16
17
  export function hasPslInputs(config: SchemaInputConfig): boolean {
@@ -24,9 +25,11 @@ export function resolveSchemaInputs(config: SchemaInputConfig): SchemaInputSet {
24
25
 
25
26
  return {
26
27
  includes: (uri) => uris.has(uri),
28
+ uris: () => uris,
27
29
  };
28
30
  }
29
31
 
30
32
  export const emptySchemaInputSet: SchemaInputSet = {
31
33
  includes: () => false,
34
+ uris: () => [],
32
35
  };
@@ -86,7 +86,7 @@ export const semanticTokensLegend: SemanticTokensLegend = {
86
86
  export interface SemanticTokenSource {
87
87
  readonly document: DocumentAst;
88
88
  readonly sourceFile: SourceFile;
89
- readonly symbolTable: SymbolTable | undefined;
89
+ readonly symbolTable: SymbolTable;
90
90
  readonly scalarTypes: readonly string[];
91
91
  }
92
92
 
@@ -528,7 +528,7 @@ function classifyTypeReference(
528
528
  const table = source.symbolTable;
529
529
  const namespaceName = path.length > 1 ? path[path.length - 2] : namespace;
530
530
  const namespaceScope =
531
- namespaceName !== undefined ? table?.topLevel.namespaces[namespaceName] : undefined;
531
+ namespaceName !== undefined ? table.topLevel.namespaces[namespaceName] : undefined;
532
532
 
533
533
  if (namespaceScope !== undefined) {
534
534
  if (Object.hasOwn(namespaceScope.models, name)) {
@@ -542,22 +542,20 @@ function classifyTypeReference(
542
542
  }
543
543
  }
544
544
 
545
- if (table !== undefined) {
546
- if (Object.hasOwn(table.topLevel.models, name)) {
547
- return { tokenType: 'class' };
548
- }
549
- if (Object.hasOwn(table.topLevel.compositeTypes, name)) {
550
- return { tokenType: 'struct' };
551
- }
552
- if (Object.hasOwn(table.topLevel.scalars, name)) {
553
- return { tokenType: 'type', modifierBitset: semanticTokenModifierBits.defaultLibrary };
554
- }
555
- if (
556
- Object.hasOwn(table.topLevel.typeAliases, name) ||
557
- Object.hasOwn(table.topLevel.blocks, name)
558
- ) {
559
- return { tokenType: 'type' };
560
- }
545
+ if (Object.hasOwn(table.topLevel.models, name)) {
546
+ return { tokenType: 'class' };
547
+ }
548
+ if (Object.hasOwn(table.topLevel.compositeTypes, name)) {
549
+ return { tokenType: 'struct' };
550
+ }
551
+ if (Object.hasOwn(table.topLevel.scalars, name)) {
552
+ return { tokenType: 'type', modifierBitset: semanticTokenModifierBits.defaultLibrary };
553
+ }
554
+ if (
555
+ Object.hasOwn(table.topLevel.typeAliases, name) ||
556
+ Object.hasOwn(table.topLevel.blocks, name)
557
+ ) {
558
+ return { tokenType: 'type' };
561
559
  }
562
560
 
563
561
  if (source.scalarTypes.includes(name)) {
@@ -567,8 +565,8 @@ function classifyTypeReference(
567
565
  return { tokenType: 'type' };
568
566
  }
569
567
 
570
- function isKnownNamespace(name: string, table: SymbolTable | undefined): boolean {
571
- return table !== undefined && Object.hasOwn(table.topLevel.namespaces, name);
568
+ function isKnownNamespace(name: string, table: SymbolTable): boolean {
569
+ return Object.hasOwn(table.topLevel.namespaces, name);
572
570
  }
573
571
 
574
572
  function identifierSegments(name: QualifiedNameAst): readonly IdentifierSegment[] {