@volar/language-core 2.3.0-alpha.4 → 2.3.0-alpha.6

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/index.d.ts CHANGED
@@ -3,9 +3,6 @@ export * from './lib/editorFeatures';
3
3
  export * from './lib/linkedCodeMap';
4
4
  export * from './lib/types';
5
5
  export * from './lib/utils';
6
- import { SourceMap } from '@volar/source-map';
7
- import type * as ts from 'typescript';
8
- import type { CodeInformation, Language, LanguagePlugin, SourceScript, VirtualCode } from './lib/types';
6
+ import type { Language, LanguagePlugin, SourceScript, VirtualCode } from './lib/types';
9
7
  export declare function createLanguage<T>(plugins: LanguagePlugin<T>[], scriptRegistry: Map<T, SourceScript<T>>, sync: (id: T) => void): Language<T>;
10
- export declare function updateVirtualCodeMapOfMap<T>(virtualCode: VirtualCode, mapOfMap: Map<T, [ts.IScriptSnapshot, SourceMap<CodeInformation>]>, getSourceSnapshot: (source: string | undefined) => [T, ts.IScriptSnapshot] | undefined): void;
11
- export declare function forEachEmbeddedCode(virtualCode: VirtualCode): Generator<VirtualCode>;
8
+ export declare function forEachEmbeddedCode<T>(virtualCode: VirtualCode<T>): Generator<VirtualCode<T>>;
package/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.forEachEmbeddedCode = exports.updateVirtualCodeMapOfMap = exports.createLanguage = void 0;
17
+ exports.forEachEmbeddedCode = exports.createLanguage = void 0;
18
18
  __exportStar(require("@volar/source-map"), exports);
19
19
  __exportStar(require("./lib/editorFeatures"), exports);
20
20
  __exportStar(require("./lib/linkedCodeMap"), exports);
@@ -23,12 +23,15 @@ __exportStar(require("./lib/utils"), exports);
23
23
  const source_map_1 = require("@volar/source-map");
24
24
  const linkedCodeMap_1 = require("./lib/linkedCodeMap");
25
25
  function createLanguage(plugins, scriptRegistry, sync) {
26
- const virtualCodeToSourceFileMap = new WeakMap();
27
- const virtualCodeToMaps = new WeakMap();
26
+ const virtualCodeToSourceScriptMap = new WeakMap();
27
+ const virtualCodeToSourceMap = new WeakMap();
28
28
  const virtualCodeToLinkedCodeMap = new WeakMap();
29
29
  return {
30
30
  plugins,
31
31
  scripts: {
32
+ fromVirtualCode(virtualCode) {
33
+ return virtualCodeToSourceScriptMap.get(virtualCode);
34
+ },
32
35
  get(id) {
33
36
  sync(id);
34
37
  return scriptRegistry.get(id);
@@ -53,16 +56,20 @@ function createLanguage(plugins, scriptRegistry, sync) {
53
56
  this.delete(id);
54
57
  return this.set(id, snapshot, languageId);
55
58
  }
56
- else if (sourceScript.snapshot !== snapshot) {
59
+ else if (sourceScript.isAssociationDirty || sourceScript.snapshot !== snapshot) {
57
60
  // snapshot updated
58
61
  sourceScript.snapshot = snapshot;
62
+ const codegenCtx = prepareCreateVirtualCode(sourceScript);
59
63
  if (sourceScript.generated) {
60
- const newVirtualCode = sourceScript.generated.languagePlugin.updateVirtualCode?.(id, sourceScript.generated.root, snapshot);
64
+ const { updateVirtualCode, createVirtualCode } = sourceScript.generated.languagePlugin;
65
+ const newVirtualCode = updateVirtualCode
66
+ ? updateVirtualCode(id, sourceScript.generated.root, snapshot, codegenCtx)
67
+ : createVirtualCode?.(id, languageId, snapshot, codegenCtx);
61
68
  if (newVirtualCode) {
62
69
  sourceScript.generated.root = newVirtualCode;
63
70
  sourceScript.generated.embeddedCodes.clear();
64
71
  for (const code of forEachEmbeddedCode(sourceScript.generated.root)) {
65
- virtualCodeToSourceFileMap.set(code, sourceScript);
72
+ virtualCodeToSourceScriptMap.set(code, sourceScript);
66
73
  sourceScript.generated.embeddedCodes.set(code.id, code);
67
74
  }
68
75
  return sourceScript;
@@ -72,6 +79,7 @@ function createLanguage(plugins, scriptRegistry, sync) {
72
79
  return;
73
80
  }
74
81
  }
82
+ triggerTargetsDirty(sourceScript);
75
83
  }
76
84
  else {
77
85
  // not changed
@@ -80,10 +88,16 @@ function createLanguage(plugins, scriptRegistry, sync) {
80
88
  }
81
89
  else {
82
90
  // created
83
- const sourceScript = { id, languageId, snapshot };
91
+ const sourceScript = {
92
+ id: id,
93
+ languageId,
94
+ snapshot,
95
+ associatedIds: new Set(),
96
+ targetIds: new Set(),
97
+ };
84
98
  scriptRegistry.set(id, sourceScript);
85
99
  for (const languagePlugin of _plugins) {
86
- const virtualCode = languagePlugin.createVirtualCode?.(id, languageId, snapshot);
100
+ const virtualCode = languagePlugin.createVirtualCode?.(id, languageId, snapshot, prepareCreateVirtualCode(sourceScript));
87
101
  if (virtualCode) {
88
102
  sourceScript.generated = {
89
103
  root: virtualCode,
@@ -91,7 +105,7 @@ function createLanguage(plugins, scriptRegistry, sync) {
91
105
  embeddedCodes: new Map(),
92
106
  };
93
107
  for (const code of forEachEmbeddedCode(virtualCode)) {
94
- virtualCodeToSourceFileMap.set(code, sourceScript);
108
+ virtualCodeToSourceScriptMap.set(code, sourceScript);
95
109
  sourceScript.generated.embeddedCodes.set(code.id, code);
96
110
  }
97
111
  break;
@@ -101,86 +115,88 @@ function createLanguage(plugins, scriptRegistry, sync) {
101
115
  }
102
116
  },
103
117
  delete(id) {
104
- const value = scriptRegistry.get(id);
105
- if (value) {
106
- if (value.generated) {
107
- value.generated.languagePlugin.disposeVirtualCode?.(id, value.generated.root);
108
- }
118
+ const sourceScript = scriptRegistry.get(id);
119
+ if (sourceScript) {
120
+ sourceScript.generated?.languagePlugin.disposeVirtualCode?.(id, sourceScript.generated.root);
109
121
  scriptRegistry.delete(id);
122
+ triggerTargetsDirty(sourceScript);
110
123
  }
111
124
  },
112
125
  },
113
126
  maps: {
114
- get(virtualCode, scriptId) {
115
- if (!scriptId) {
116
- const sourceScript = virtualCodeToSourceFileMap.get(virtualCode);
117
- if (!sourceScript) {
118
- return;
119
- }
120
- scriptId = sourceScript.id;
121
- }
122
- for (const [id, [_snapshot, map]] of this.forEach(virtualCode)) {
123
- if (id === scriptId) {
124
- return map;
125
- }
127
+ get(virtualCode) {
128
+ for (const map of this.forEach(virtualCode)) {
129
+ return map[2];
126
130
  }
131
+ throw `no map found for ${virtualCode.id}`;
127
132
  },
128
- forEach(virtualCode) {
129
- let map = virtualCodeToMaps.get(virtualCode.snapshot);
130
- if (!map) {
131
- map = new Map();
132
- virtualCodeToMaps.set(virtualCode.snapshot, map);
133
+ *forEach(virtualCode) {
134
+ let mapCache = virtualCodeToSourceMap.get(virtualCode.snapshot);
135
+ if (!mapCache) {
136
+ virtualCodeToSourceMap.set(virtualCode.snapshot, mapCache = new WeakMap());
133
137
  }
134
- updateVirtualCodeMapOfMap(virtualCode, map, id => {
135
- if (id) {
136
- throw 'not implemented';
137
- // const sourceScript = sourceScripts.get(id)!;
138
- // return [id, sourceScript.snapshot];
139
- }
140
- else {
141
- const sourceScript = virtualCodeToSourceFileMap.get(virtualCode);
142
- return [sourceScript.id, sourceScript.snapshot];
138
+ const sourceScript = virtualCodeToSourceScriptMap.get(virtualCode);
139
+ if (!mapCache.has(sourceScript.snapshot)) {
140
+ mapCache.set(sourceScript.snapshot, new source_map_1.SourceMap(virtualCode.mappings));
141
+ }
142
+ yield [sourceScript.id, sourceScript.snapshot, mapCache.get(sourceScript.snapshot)];
143
+ if (virtualCode.associatedScriptMappings) {
144
+ for (const [relatedScriptId, relatedMappings] of virtualCode.associatedScriptMappings) {
145
+ const relatedSourceScript = scriptRegistry.get(relatedScriptId);
146
+ if (relatedSourceScript) {
147
+ if (!mapCache.has(relatedSourceScript.snapshot)) {
148
+ mapCache.set(relatedSourceScript.snapshot, new source_map_1.SourceMap(relatedMappings));
149
+ }
150
+ yield [relatedSourceScript.id, relatedSourceScript.snapshot, mapCache.get(relatedSourceScript.snapshot)];
151
+ }
143
152
  }
144
- });
145
- return map;
153
+ }
146
154
  },
147
155
  },
148
156
  linkedCodeMaps: {
149
157
  get(virtualCode) {
150
- if (!virtualCodeToLinkedCodeMap.has(virtualCode.snapshot)) {
151
- virtualCodeToLinkedCodeMap.set(virtualCode.snapshot, virtualCode.linkedCodeMappings
152
- ? new linkedCodeMap_1.LinkedCodeMap(virtualCode.linkedCodeMappings)
153
- : undefined);
158
+ const sourceScript = virtualCodeToSourceScriptMap.get(virtualCode);
159
+ let mapCache = virtualCodeToLinkedCodeMap.get(virtualCode.snapshot);
160
+ if (mapCache?.[0] !== sourceScript.snapshot) {
161
+ virtualCodeToLinkedCodeMap.set(virtualCode.snapshot, mapCache = [
162
+ sourceScript.snapshot,
163
+ virtualCode.linkedCodeMappings
164
+ ? new linkedCodeMap_1.LinkedCodeMap(virtualCode.linkedCodeMappings)
165
+ : undefined,
166
+ ]);
154
167
  }
155
- return virtualCodeToLinkedCodeMap.get(virtualCode.snapshot);
168
+ return mapCache[1];
156
169
  },
157
170
  },
158
171
  };
159
- }
160
- exports.createLanguage = createLanguage;
161
- function updateVirtualCodeMapOfMap(virtualCode, mapOfMap, getSourceSnapshot) {
162
- const sources = new Set();
163
- if (!virtualCode.mappings.length) {
164
- const source = getSourceSnapshot(undefined);
165
- if (source) {
166
- mapOfMap.set(source[0], [source[1], new source_map_1.SourceMap([])]);
167
- }
172
+ function triggerTargetsDirty(sourceScript) {
173
+ sourceScript.targetIds.forEach(id => {
174
+ const sourceScript = scriptRegistry.get(id);
175
+ if (sourceScript) {
176
+ sourceScript.isAssociationDirty = true;
177
+ }
178
+ });
168
179
  }
169
- for (const mapping of virtualCode.mappings) {
170
- if (sources.has(mapping.source)) {
171
- continue;
172
- }
173
- sources.add(mapping.source);
174
- const source = getSourceSnapshot(mapping.source);
175
- if (!source) {
176
- continue;
177
- }
178
- if (!mapOfMap.has(source[0]) || mapOfMap.get(source[0])[0] !== source[1]) {
179
- mapOfMap.set(source[0], [source[1], new source_map_1.SourceMap(virtualCode.mappings.filter(mapping2 => mapping2.source === mapping.source))]);
180
+ function prepareCreateVirtualCode(sourceScript) {
181
+ for (const id of sourceScript.associatedIds) {
182
+ scriptRegistry.get(id)?.targetIds.delete(sourceScript.id);
180
183
  }
184
+ sourceScript.associatedIds.clear();
185
+ sourceScript.isAssociationDirty = false;
186
+ return {
187
+ getAssociatedScript(id) {
188
+ sync(id);
189
+ const relatedSourceScript = scriptRegistry.get(id);
190
+ if (relatedSourceScript) {
191
+ relatedSourceScript.targetIds.add(sourceScript.id);
192
+ sourceScript.associatedIds.add(relatedSourceScript.id);
193
+ }
194
+ return relatedSourceScript;
195
+ },
196
+ };
181
197
  }
182
198
  }
183
- exports.updateVirtualCodeMapOfMap = updateVirtualCodeMapOfMap;
199
+ exports.createLanguage = createLanguage;
184
200
  function* forEachEmbeddedCode(virtualCode) {
185
201
  yield virtualCode;
186
202
  if (virtualCode.embeddedCodes) {
@@ -1,4 +1,4 @@
1
1
  import { SourceMap } from '@volar/source-map';
2
- export declare class LinkedCodeMap extends SourceMap {
2
+ export declare class LinkedCodeMap extends SourceMap<any> {
3
3
  getLinkedOffsets(start: number): Generator<number, void, unknown>;
4
4
  }
package/lib/types.d.ts CHANGED
@@ -1,19 +1,20 @@
1
1
  import type { Mapping, SourceMap } from '@volar/source-map';
2
2
  import type * as ts from 'typescript';
3
3
  import type { LinkedCodeMap } from './linkedCodeMap';
4
- export interface Language<T> {
4
+ export interface Language<T = unknown> {
5
5
  plugins: LanguagePlugin<T>[];
6
6
  scripts: {
7
7
  get(id: T): SourceScript<T> | undefined;
8
8
  set(id: T, snapshot: ts.IScriptSnapshot, languageId?: string, plugins?: LanguagePlugin<T>[]): SourceScript<T> | undefined;
9
9
  delete(id: T): void;
10
+ fromVirtualCode(virtualCode: VirtualCode<T>): SourceScript<T>;
10
11
  };
11
12
  maps: {
12
- get(virtualCode: VirtualCode, scriptId?: T): SourceMap<CodeInformation> | undefined;
13
- forEach(virtualCode: VirtualCode): Map<T, [ts.IScriptSnapshot, SourceMap<CodeInformation>]>;
13
+ get(virtualCode: VirtualCode<T>): SourceMap<CodeInformation>;
14
+ forEach(virtualCode: VirtualCode<T>): Generator<[id: T, snapshot: ts.IScriptSnapshot, map: SourceMap<CodeInformation>]>;
14
15
  };
15
16
  linkedCodeMaps: {
16
- get(virtualCode: VirtualCode): LinkedCodeMap | undefined;
17
+ get(virtualCode: VirtualCode<T>): LinkedCodeMap | undefined;
17
18
  };
18
19
  typescript?: {
19
20
  configFileName: string | undefined;
@@ -22,28 +23,32 @@ export interface Language<T> {
22
23
  sync?(): Promise<number>;
23
24
  };
24
25
  languageServiceHost: ts.LanguageServiceHost;
25
- getExtraServiceScript(fileName: string): [SourceScript<T>, TypeScriptExtraServiceScript] | undefined;
26
+ getExtraServiceScript(fileName: string): TypeScriptExtraServiceScript<T> | undefined;
26
27
  asScriptId(fileName: string): T;
27
28
  asFileName(scriptId: T): string;
28
29
  };
29
30
  }
30
- export interface SourceScript<T> {
31
+ export interface SourceScript<T = unknown> {
31
32
  id: T;
32
33
  languageId: string;
33
34
  snapshot: ts.IScriptSnapshot;
35
+ targetIds: Set<T>;
36
+ associatedIds: Set<T>;
37
+ isAssociationDirty?: boolean;
34
38
  generated?: {
35
- root: VirtualCode;
39
+ root: VirtualCode<T>;
36
40
  languagePlugin: LanguagePlugin<T>;
37
- embeddedCodes: Map<string, VirtualCode>;
41
+ embeddedCodes: Map<string, VirtualCode<T>>;
38
42
  };
39
43
  }
40
44
  export type CodeMapping = Mapping<CodeInformation>;
41
- export interface VirtualCode {
45
+ export interface VirtualCode<T = unknown> {
42
46
  id: string;
43
47
  languageId: string;
44
48
  snapshot: ts.IScriptSnapshot;
45
49
  mappings: CodeMapping[];
46
- embeddedCodes?: VirtualCode[];
50
+ associatedScriptMappings?: Map<T, CodeMapping[]>;
51
+ embeddedCodes?: VirtualCode<T>[];
47
52
  linkedCodeMappings?: Mapping[];
48
53
  }
49
54
  export interface CodeInformation {
@@ -71,39 +76,51 @@ export interface CodeInformation {
71
76
  /** virtual code is expected correctly reflect the format information of the source code */
72
77
  format?: boolean;
73
78
  }
74
- export interface TypeScriptServiceScript {
75
- code: VirtualCode;
79
+ export interface TypeScriptServiceScript<T = unknown> {
80
+ code: VirtualCode<T>;
76
81
  extension: '.ts' | '.js' | '.mts' | '.mjs' | '.cjs' | '.cts' | '.d.ts' | string;
77
82
  scriptKind: ts.ScriptKind;
83
+ /** See #188 */
84
+ preventLeadingOffset?: boolean;
78
85
  }
79
- export interface TypeScriptExtraServiceScript extends TypeScriptServiceScript {
86
+ export interface TypeScriptExtraServiceScript<T = unknown> extends TypeScriptServiceScript<T> {
80
87
  fileName: string;
81
88
  }
82
- export interface LanguagePlugin<T, K extends VirtualCode = VirtualCode> {
89
+ export interface LanguagePlugin<T = unknown, K extends VirtualCode<T> = VirtualCode<T>> {
90
+ /**
91
+ * For files that are not opened in the IDE, the language ID will not be synchronized to the language server, so a hook is needed to parse the language ID of files that are known extension but not opened in the IDE.
92
+ */
83
93
  getLanguageId(scriptId: T): string | undefined;
84
- createVirtualCode?(scriptId: T, languageId: string, snapshot: ts.IScriptSnapshot): K | undefined;
85
- updateVirtualCode?(scriptId: T, virtualCode: K, newSnapshot: ts.IScriptSnapshot): K | undefined;
94
+ /**
95
+ * Generate a virtual code.
96
+ */
97
+ createVirtualCode?(scriptId: T, languageId: string, snapshot: ts.IScriptSnapshot, ctx: CodegenContext<T>): K | undefined;
98
+ /**
99
+ * Incremental update a virtual code. If not provide, call createVirtualCode again.
100
+ */
101
+ updateVirtualCode?(scriptId: T, virtualCode: K, newSnapshot: ts.IScriptSnapshot, ctx: CodegenContext<T>): K | undefined;
102
+ /**
103
+ * Cleanup a virtual code.
104
+ */
86
105
  disposeVirtualCode?(scriptId: T, virtualCode: K): void;
87
- typescript?: {
88
- /**
89
- * LSP + TS Plugin
90
- */
91
- extraFileExtensions: ts.FileExtensionInfo[];
92
- /**
93
- * LSP + TS Plugin
94
- */
95
- resolveHiddenExtensions?: boolean;
96
- /**
97
- * LSP + TS Plugin
98
- */
99
- getServiceScript(rootVirtualCode: K): TypeScriptServiceScript | undefined;
100
- /**
101
- * LSP only
102
- */
103
- getExtraServiceScripts?(fileName: string, rootVirtualCode: K): TypeScriptExtraServiceScript[];
104
- /**
105
- * LSP only
106
- */
107
- resolveLanguageServiceHost?(host: ts.LanguageServiceHost): ts.LanguageServiceHost;
108
- };
106
+ typescript?: TypeScriptGenericOptions<T, K> & TypeScriptNonTSPluginOptions<T, K>;
107
+ }
108
+ export interface CodegenContext<T = unknown> {
109
+ getAssociatedScript(scriptId: T): SourceScript<T> | undefined;
110
+ }
111
+ /**
112
+ * The following options available to all situations.
113
+ */
114
+ interface TypeScriptGenericOptions<T, K> {
115
+ extraFileExtensions: ts.FileExtensionInfo[];
116
+ resolveHiddenExtensions?: boolean;
117
+ getServiceScript(root: K): TypeScriptServiceScript<T> | undefined;
118
+ }
119
+ /**
120
+ * The following options will not be available in TS plugin.
121
+ */
122
+ interface TypeScriptNonTSPluginOptions<T, K> {
123
+ getExtraServiceScripts?(fileName: string, rootVirtualCode: K): TypeScriptExtraServiceScript<T>[];
124
+ resolveLanguageServiceHost?(host: ts.LanguageServiceHost): ts.LanguageServiceHost;
109
125
  }
126
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volar/language-core",
3
- "version": "2.3.0-alpha.4",
3
+ "version": "2.3.0-alpha.6",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "**/*.js",
@@ -12,7 +12,7 @@
12
12
  "directory": "packages/language-core"
13
13
  },
14
14
  "dependencies": {
15
- "@volar/source-map": "2.3.0-alpha.4"
15
+ "@volar/source-map": "2.3.0-alpha.6"
16
16
  },
17
- "gitHead": "8ab3ce02b06a410e9321eea3e9f23d36f97fedda"
17
+ "gitHead": "f3103e86be8d80cb36f66be4c054ef9a255c29b1"
18
18
  }