@volar/language-core 2.3.0-alpha.0 → 2.3.0-alpha.10

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
8
  export declare function forEachEmbeddedCode(virtualCode: VirtualCode): Generator<VirtualCode>;
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,14 +23,22 @@ __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);
37
+ const result = scriptRegistry.get(id);
38
+ // The sync function provider may not always call the set function due to caching, so it is necessary to explicitly check isAssociationDirty.
39
+ if (result?.isAssociationDirty) {
40
+ this.set(id, result.snapshot, result.languageId);
41
+ }
34
42
  return scriptRegistry.get(id);
35
43
  },
36
44
  set(id, snapshot, languageId, _plugins = plugins) {
@@ -46,23 +54,36 @@ function createLanguage(plugins, scriptRegistry, sync) {
46
54
  console.warn(`languageId not found for ${id}`);
47
55
  return;
48
56
  }
57
+ let associatedOnly = false;
58
+ for (const plugin of plugins) {
59
+ if (plugin.isAssociatedFileOnly?.(id, languageId)) {
60
+ associatedOnly = true;
61
+ break;
62
+ }
63
+ }
49
64
  if (scriptRegistry.has(id)) {
50
65
  const sourceScript = scriptRegistry.get(id);
51
- if (sourceScript.languageId !== languageId) {
52
- // languageId changed
66
+ if (sourceScript.languageId !== languageId || sourceScript.associatedOnly !== associatedOnly) {
53
67
  this.delete(id);
54
68
  return this.set(id, snapshot, languageId);
55
69
  }
56
- else if (sourceScript.snapshot !== snapshot) {
70
+ else if (associatedOnly) {
71
+ sourceScript.snapshot = snapshot;
72
+ }
73
+ else if (sourceScript.isAssociationDirty || sourceScript.snapshot !== snapshot) {
57
74
  // snapshot updated
58
75
  sourceScript.snapshot = snapshot;
76
+ const codegenCtx = prepareCreateVirtualCode(sourceScript);
59
77
  if (sourceScript.generated) {
60
- const newVirtualCode = sourceScript.generated.languagePlugin.updateVirtualCode?.(id, sourceScript.generated.root, snapshot);
78
+ const { updateVirtualCode, createVirtualCode } = sourceScript.generated.languagePlugin;
79
+ const newVirtualCode = updateVirtualCode
80
+ ? updateVirtualCode(id, sourceScript.generated.root, snapshot, codegenCtx)
81
+ : createVirtualCode?.(id, languageId, snapshot, codegenCtx);
61
82
  if (newVirtualCode) {
62
83
  sourceScript.generated.root = newVirtualCode;
63
84
  sourceScript.generated.embeddedCodes.clear();
64
85
  for (const code of forEachEmbeddedCode(sourceScript.generated.root)) {
65
- virtualCodeToSourceFileMap.set(code, sourceScript);
86
+ virtualCodeToSourceScriptMap.set(code, sourceScript);
66
87
  sourceScript.generated.embeddedCodes.set(code.id, code);
67
88
  }
68
89
  return sourceScript;
@@ -72,6 +93,7 @@ function createLanguage(plugins, scriptRegistry, sync) {
72
93
  return;
73
94
  }
74
95
  }
96
+ triggerTargetsDirty(sourceScript);
75
97
  }
76
98
  else {
77
99
  // not changed
@@ -80,10 +102,20 @@ function createLanguage(plugins, scriptRegistry, sync) {
80
102
  }
81
103
  else {
82
104
  // created
83
- const sourceScript = { id, languageId, snapshot };
105
+ const sourceScript = {
106
+ id: id,
107
+ languageId,
108
+ snapshot,
109
+ associatedIds: new Set(),
110
+ targetIds: new Set(),
111
+ associatedOnly
112
+ };
84
113
  scriptRegistry.set(id, sourceScript);
114
+ if (associatedOnly) {
115
+ return sourceScript;
116
+ }
85
117
  for (const languagePlugin of _plugins) {
86
- const virtualCode = languagePlugin.createVirtualCode?.(id, languageId, snapshot);
118
+ const virtualCode = languagePlugin.createVirtualCode?.(id, languageId, snapshot, prepareCreateVirtualCode(sourceScript));
87
119
  if (virtualCode) {
88
120
  sourceScript.generated = {
89
121
  root: virtualCode,
@@ -91,7 +123,7 @@ function createLanguage(plugins, scriptRegistry, sync) {
91
123
  embeddedCodes: new Map(),
92
124
  };
93
125
  for (const code of forEachEmbeddedCode(virtualCode)) {
94
- virtualCodeToSourceFileMap.set(code, sourceScript);
126
+ virtualCodeToSourceScriptMap.set(code, sourceScript);
95
127
  sourceScript.generated.embeddedCodes.set(code.id, code);
96
128
  }
97
129
  break;
@@ -101,86 +133,90 @@ function createLanguage(plugins, scriptRegistry, sync) {
101
133
  }
102
134
  },
103
135
  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
- }
136
+ const sourceScript = scriptRegistry.get(id);
137
+ if (sourceScript) {
138
+ sourceScript.generated?.languagePlugin.disposeVirtualCode?.(id, sourceScript.generated.root);
109
139
  scriptRegistry.delete(id);
140
+ triggerTargetsDirty(sourceScript);
110
141
  }
111
142
  },
112
143
  },
113
144
  maps: {
114
- get(virtualCode, scriptId) {
115
- if (!scriptId) {
116
- const sourceScript = virtualCodeToSourceFileMap.get(virtualCode);
117
- if (!sourceScript) {
118
- return;
119
- }
120
- scriptId = sourceScript.id;
145
+ get(virtualCode, sourceScript, mappings) {
146
+ let mapCache = virtualCodeToSourceMap.get(virtualCode.snapshot);
147
+ if (!mapCache) {
148
+ virtualCodeToSourceMap.set(virtualCode.snapshot, mapCache = new WeakMap());
121
149
  }
122
- for (const [id, [_snapshot, map]] of this.forEach(virtualCode)) {
123
- if (id === scriptId) {
124
- return map;
125
- }
150
+ if (!mapCache.has(sourceScript.snapshot)) {
151
+ mapCache.set(sourceScript.snapshot, new source_map_1.SourceMap(mappings ?? virtualCode.mappings));
126
152
  }
153
+ return mapCache.get(sourceScript.snapshot);
127
154
  },
128
- forEach(virtualCode) {
129
- let map = virtualCodeToMaps.get(virtualCode.snapshot);
130
- if (!map) {
131
- map = new Map();
132
- virtualCodeToMaps.set(virtualCode.snapshot, map);
133
- }
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];
155
+ *forEach(virtualCode) {
156
+ const sourceScript = virtualCodeToSourceScriptMap.get(virtualCode);
157
+ yield [
158
+ sourceScript.id,
159
+ sourceScript.snapshot,
160
+ this.get(virtualCode, sourceScript),
161
+ ];
162
+ if (virtualCode.associatedScriptMappings) {
163
+ for (const [relatedScriptId, relatedMappings] of virtualCode.associatedScriptMappings) {
164
+ const relatedSourceScript = scriptRegistry.get(relatedScriptId);
165
+ if (relatedSourceScript) {
166
+ yield [
167
+ relatedSourceScript.id,
168
+ relatedSourceScript.snapshot,
169
+ this.get(virtualCode, relatedSourceScript, relatedMappings),
170
+ ];
171
+ }
143
172
  }
144
- });
145
- return map;
173
+ }
146
174
  },
147
175
  },
148
176
  linkedCodeMaps: {
149
177
  get(virtualCode) {
150
- if (!virtualCodeToLinkedCodeMap.has(virtualCode.snapshot)) {
151
- virtualCodeToLinkedCodeMap.set(virtualCode.snapshot, virtualCode.linkedCodeMappings
152
- ? new linkedCodeMap_1.LinkedCodeMap(virtualCode.linkedCodeMappings)
153
- : undefined);
178
+ const sourceScript = virtualCodeToSourceScriptMap.get(virtualCode);
179
+ let mapCache = virtualCodeToLinkedCodeMap.get(virtualCode.snapshot);
180
+ if (mapCache?.[0] !== sourceScript.snapshot) {
181
+ virtualCodeToLinkedCodeMap.set(virtualCode.snapshot, mapCache = [
182
+ sourceScript.snapshot,
183
+ virtualCode.linkedCodeMappings
184
+ ? new linkedCodeMap_1.LinkedCodeMap(virtualCode.linkedCodeMappings)
185
+ : undefined,
186
+ ]);
154
187
  }
155
- return virtualCodeToLinkedCodeMap.get(virtualCode.snapshot);
188
+ return mapCache[1];
156
189
  },
157
190
  },
158
191
  };
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
- }
192
+ function triggerTargetsDirty(sourceScript) {
193
+ sourceScript.targetIds.forEach(id => {
194
+ const sourceScript = scriptRegistry.get(id);
195
+ if (sourceScript) {
196
+ sourceScript.isAssociationDirty = true;
197
+ }
198
+ });
168
199
  }
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))]);
200
+ function prepareCreateVirtualCode(sourceScript) {
201
+ for (const id of sourceScript.associatedIds) {
202
+ scriptRegistry.get(id)?.targetIds.delete(sourceScript.id);
180
203
  }
204
+ sourceScript.associatedIds.clear();
205
+ sourceScript.isAssociationDirty = false;
206
+ return {
207
+ getAssociatedScript(id) {
208
+ sync(id);
209
+ const relatedSourceScript = scriptRegistry.get(id);
210
+ if (relatedSourceScript) {
211
+ relatedSourceScript.targetIds.add(sourceScript.id);
212
+ sourceScript.associatedIds.add(relatedSourceScript.id);
213
+ }
214
+ return relatedSourceScript;
215
+ },
216
+ };
181
217
  }
182
218
  }
183
- exports.updateVirtualCodeMapOfMap = updateVirtualCodeMapOfMap;
219
+ exports.createLanguage = createLanguage;
184
220
  function* forEachEmbeddedCode(virtualCode) {
185
221
  yield virtualCode;
186
222
  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,16 +1,17 @@
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): 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, sourceScript: SourceScript<T>, mappings?: Mapping<CodeInformation>[]): SourceMap<CodeInformation>;
14
+ forEach(virtualCode: VirtualCode): Generator<[id: T, snapshot: ts.IScriptSnapshot, map: SourceMap<CodeInformation>]>;
14
15
  };
15
16
  linkedCodeMaps: {
16
17
  get(virtualCode: VirtualCode): LinkedCodeMap | undefined;
@@ -27,10 +28,14 @@ export interface Language<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
+ associatedOnly: boolean;
38
+ isAssociationDirty?: boolean;
34
39
  generated?: {
35
40
  root: VirtualCode;
36
41
  languagePlugin: LanguagePlugin<T>;
@@ -43,6 +48,7 @@ export interface VirtualCode {
43
48
  languageId: string;
44
49
  snapshot: ts.IScriptSnapshot;
45
50
  mappings: CodeMapping[];
51
+ associatedScriptMappings?: Map<unknown, CodeMapping[]>;
46
52
  embeddedCodes?: VirtualCode[];
47
53
  linkedCodeMappings?: Mapping[];
48
54
  }
@@ -75,31 +81,54 @@ export interface TypeScriptServiceScript {
75
81
  code: VirtualCode;
76
82
  extension: '.ts' | '.js' | '.mts' | '.mjs' | '.cjs' | '.cts' | '.d.ts' | string;
77
83
  scriptKind: ts.ScriptKind;
84
+ /** See #188 */
85
+ preventLeadingOffset?: boolean;
78
86
  }
79
87
  export interface TypeScriptExtraServiceScript extends TypeScriptServiceScript {
80
88
  fileName: string;
81
89
  }
82
- export interface LanguagePlugin<T, K extends VirtualCode = VirtualCode> {
90
+ export interface LanguagePlugin<T = unknown, K extends VirtualCode = VirtualCode> {
91
+ /**
92
+ * 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.
93
+ */
83
94
  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;
95
+ /**
96
+ * Generate a virtual code.
97
+ */
98
+ createVirtualCode?(scriptId: T, languageId: string, snapshot: ts.IScriptSnapshot, ctx: CodegenContext<T>): K | undefined;
99
+ /**
100
+ * Incremental update a virtual code. If not provide, call createVirtualCode again.
101
+ */
102
+ updateVirtualCode?(scriptId: T, virtualCode: K, newSnapshot: ts.IScriptSnapshot, ctx: CodegenContext<T>): K | undefined;
103
+ /**
104
+ * Cleanup a virtual code.
105
+ */
86
106
  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
- getServiceScript(rootVirtualCode: K): TypeScriptServiceScript | undefined;
96
- /**
97
- * LSP only
98
- */
99
- getExtraServiceScripts?(fileName: string, rootVirtualCode: K): TypeScriptExtraServiceScript[];
100
- /**
101
- * LSP only
102
- */
103
- resolveLanguageServiceHost?(host: ts.LanguageServiceHost): ts.LanguageServiceHost;
104
- };
107
+ /**
108
+ * Some file types should not be parsed or processed as TypeScript files,
109
+ * as they are used only as sources for generated files.
110
+ *
111
+ * This functionality is required only in TS plugin mode.
112
+ */
113
+ isAssociatedFileOnly?(scriptId: T, languageId: string): boolean;
114
+ typescript?: TypeScriptGenericOptions<K> & TypeScriptNonTSPluginOptions<K>;
115
+ }
116
+ export interface CodegenContext<T = unknown> {
117
+ getAssociatedScript(scriptId: T): SourceScript<T> | undefined;
118
+ }
119
+ /**
120
+ * The following options available to all situations.
121
+ */
122
+ interface TypeScriptGenericOptions<K> {
123
+ extraFileExtensions: ts.FileExtensionInfo[];
124
+ resolveHiddenExtensions?: boolean;
125
+ getServiceScript(root: K): TypeScriptServiceScript | undefined;
126
+ }
127
+ /**
128
+ * The following options will not be available in TS plugin.
129
+ */
130
+ interface TypeScriptNonTSPluginOptions<K> {
131
+ getExtraServiceScripts?(fileName: string, rootVirtualCode: K): TypeScriptExtraServiceScript[];
132
+ resolveLanguageServiceHost?(host: ts.LanguageServiceHost): ts.LanguageServiceHost;
105
133
  }
134
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volar/language-core",
3
- "version": "2.3.0-alpha.0",
3
+ "version": "2.3.0-alpha.10",
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.0"
15
+ "@volar/source-map": "2.3.0-alpha.10"
16
16
  },
17
- "gitHead": "f17c19f712651acde33cc2171a112e64db0b460e"
17
+ "gitHead": "44fffdc323ca3ff55818a8656a2726867df53a76"
18
18
  }