skir 1.1.2 → 1.1.4
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/dist/compatibility_checker.d.ts.map +1 -1
- package/dist/compatibility_checker.js +2 -1
- package/dist/compatibility_checker.js.map +1 -1
- package/dist/compiler.js +11 -6
- package/dist/compiler.js.map +1 -1
- package/dist/module_collector.d.ts +1 -1
- package/dist/module_collector.d.ts.map +1 -1
- package/dist/module_collector.js +11 -5
- package/dist/module_collector.js.map +1 -1
- package/dist/module_set.d.ts +25 -19
- package/dist/module_set.d.ts.map +1 -1
- package/dist/module_set.js +328 -136
- package/dist/module_set.js.map +1 -1
- package/dist/snapshotter.js +6 -4
- package/dist/snapshotter.js.map +1 -1
- package/package.json +1 -1
- package/src/compatibility_checker.ts +2 -1
- package/src/compiler.ts +17 -6
- package/src/module_collector.ts +13 -4
- package/src/module_set.ts +408 -157
- package/src/snapshotter.ts +6 -4
package/dist/module_set.js
CHANGED
|
@@ -3,48 +3,78 @@ import { unquoteAndUnescape, } from "skir-internal";
|
|
|
3
3
|
import { isStringLiteral, literalValueToDenseJson, literalValueToIdentity, valueHasPrimitiveType, } from "./literals.js";
|
|
4
4
|
import { parseModule } from "./parser.js";
|
|
5
5
|
import { tokenizeModule } from "./tokenizer.js";
|
|
6
|
+
/**
|
|
7
|
+
* Result of compiling a set of modules. Immutable.
|
|
8
|
+
*
|
|
9
|
+
* Support incremental compilation by accepting an optional cache of the previous
|
|
10
|
+
* module set. This cache can be used to avoid re-parsing and re-resolving
|
|
11
|
+
* modules that haven't changed since the last compilation.
|
|
12
|
+
*/
|
|
6
13
|
export class ModuleSet {
|
|
7
|
-
static
|
|
8
|
-
return new ModuleSet(
|
|
14
|
+
static compile(modulePathToContent, cache, parseMode = "strict") {
|
|
15
|
+
return new ModuleSet(modulePathToContent, cache, parseMode);
|
|
9
16
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
constructor(modulePathToContent, cache, parseMode) {
|
|
18
|
+
this.modulePathToContent = modulePathToContent;
|
|
19
|
+
this.parseMode = parseMode;
|
|
20
|
+
this.moduleBundles = new Map();
|
|
21
|
+
this.registry = new DeclarationRegistry();
|
|
22
|
+
this.cache = cache
|
|
23
|
+
? new Cache(modulePathToContent, cache.moduleBundles)
|
|
24
|
+
: undefined;
|
|
25
|
+
for (const modulePath of modulePathToContent.keys()) {
|
|
26
|
+
this.parseAndResolve(modulePath, new Set());
|
|
14
27
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.moduleParser = moduleParser;
|
|
19
|
-
this.mutableModules = new Map();
|
|
20
|
-
this.mutableRecordMap = new Map();
|
|
21
|
-
this.mutableResolvedModules = [];
|
|
22
|
-
this.numberToRecord = new Map();
|
|
23
|
-
this.numberToMethod = new Map();
|
|
24
|
-
this.mutableErrors = [];
|
|
28
|
+
this.finalizationResult = this.finalize();
|
|
29
|
+
// So it can be garbage collected.
|
|
30
|
+
this.cache = undefined;
|
|
25
31
|
}
|
|
26
32
|
parseAndResolve(modulePath, inProgressSet) {
|
|
27
|
-
const inMap = this.
|
|
33
|
+
const inMap = this.moduleBundles.get(modulePath);
|
|
28
34
|
if (inMap !== undefined) {
|
|
29
35
|
return inMap;
|
|
30
36
|
}
|
|
31
|
-
const result = this.doParseAndResolve(modulePath, inProgressSet
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
const result = this.doParseAndResolve(modulePath, inProgressSet);
|
|
38
|
+
if (result) {
|
|
39
|
+
this.moduleBundles.set(modulePath, result);
|
|
40
|
+
}
|
|
34
41
|
return result;
|
|
35
42
|
}
|
|
36
43
|
/** Called by `parseAndResolve` when the module is not in the map already. */
|
|
37
44
|
doParseAndResolve(modulePath, inProgressSet) {
|
|
38
|
-
const
|
|
39
|
-
|
|
45
|
+
const moduleContent = this.modulePathToContent.get(modulePath);
|
|
46
|
+
if (moduleContent === undefined) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
let moduleTokens;
|
|
40
50
|
{
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
const moduleCacheResult = this.cache?.getModuleCacheResult(modulePath) ?? { kind: "no-cache" };
|
|
52
|
+
switch (moduleCacheResult.kind) {
|
|
53
|
+
case "no-cache": {
|
|
54
|
+
moduleTokens = tokenizeModule(moduleContent, modulePath);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case "module-tokens": {
|
|
58
|
+
moduleTokens = moduleCacheResult.tokens;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "module-bundle": {
|
|
62
|
+
this.registry.mergeFrom(moduleCacheResult.bundle.registry);
|
|
63
|
+
return moduleCacheResult.bundle;
|
|
64
|
+
}
|
|
44
65
|
}
|
|
66
|
+
}
|
|
67
|
+
let module;
|
|
68
|
+
const errors = [];
|
|
69
|
+
{
|
|
70
|
+
const parseResult = parseModule(moduleTokens.result, this.parseMode);
|
|
45
71
|
errors.push(...parseResult.errors);
|
|
46
72
|
module = parseResult.result;
|
|
47
73
|
}
|
|
74
|
+
const moduleBundle = new ModuleBundle(moduleTokens, {
|
|
75
|
+
result: module,
|
|
76
|
+
errors: errors,
|
|
77
|
+
});
|
|
48
78
|
// Process all imports.
|
|
49
79
|
const pathToImports = new Map();
|
|
50
80
|
for (const declaration of module.declarations) {
|
|
@@ -76,14 +106,16 @@ export class ModuleSet {
|
|
|
76
106
|
inProgressSet.add(modulePath);
|
|
77
107
|
const otherModule = this.parseAndResolve(otherModulePath, inProgressSet);
|
|
78
108
|
inProgressSet.delete(modulePath);
|
|
79
|
-
if (otherModule
|
|
109
|
+
if (otherModule === null) {
|
|
80
110
|
errors.push({
|
|
81
111
|
token: declaration.modulePath,
|
|
82
112
|
message: "Module not found",
|
|
113
|
+
expectedNames: suggestModulePaths(unquoteAndUnescape(declaration.modulePath.text), modulePath, this.modulePathToContent),
|
|
83
114
|
});
|
|
84
115
|
}
|
|
85
|
-
else if (otherModule.errors.length !== 0
|
|
86
|
-
|
|
116
|
+
else if (otherModule.tokens.errors.length !== 0 ||
|
|
117
|
+
otherModule.module.errors.length !== 0) {
|
|
118
|
+
const hasCircularDependency = otherModule.module.errors.some((e) => e.message === circularDependencyMessage);
|
|
87
119
|
if (hasCircularDependency) {
|
|
88
120
|
errors.push({
|
|
89
121
|
token: declaration.modulePath,
|
|
@@ -102,12 +134,12 @@ export class ModuleSet {
|
|
|
102
134
|
// Make sure that the symbols we are importing exist in the imported
|
|
103
135
|
// module and are not imported symbols themselves.
|
|
104
136
|
for (const importedName of declaration.importedNames) {
|
|
105
|
-
const importedDeclaration = otherModule.result.nameToDeclaration[importedName.text];
|
|
137
|
+
const importedDeclaration = otherModule.module.result.nameToDeclaration[importedName.text];
|
|
106
138
|
if (importedDeclaration === undefined) {
|
|
107
139
|
errors.push({
|
|
108
140
|
token: importedName,
|
|
109
141
|
message: "Not found",
|
|
110
|
-
expectedNames: declarationsToExpectedNames(otherModule.result.nameToDeclaration, (d) => d.kind === "record"),
|
|
142
|
+
expectedNames: declarationsToExpectedNames(otherModule.module.result.nameToDeclaration, (d) => d.kind === "record"),
|
|
111
143
|
});
|
|
112
144
|
}
|
|
113
145
|
else if (importedDeclaration.kind === "import") {
|
|
@@ -126,7 +158,7 @@ export class ModuleSet {
|
|
|
126
158
|
}
|
|
127
159
|
}
|
|
128
160
|
const pathToImportedNames = module.pathToImportedNames;
|
|
129
|
-
for (const [path, imports] of pathToImports
|
|
161
|
+
for (const [path, imports] of pathToImports) {
|
|
130
162
|
const importsNoAlias = imports.filter((i) => i.kind === "import");
|
|
131
163
|
const importsWithAlias = imports.filter((i) => i.kind === "import-alias");
|
|
132
164
|
if (importsNoAlias.length && importsWithAlias.length) {
|
|
@@ -167,41 +199,25 @@ export class ModuleSet {
|
|
|
167
199
|
};
|
|
168
200
|
}
|
|
169
201
|
}
|
|
170
|
-
const result = {
|
|
171
|
-
result: module,
|
|
172
|
-
errors: errors,
|
|
173
|
-
};
|
|
174
202
|
if (errors.length) {
|
|
175
|
-
return
|
|
203
|
+
return moduleBundle;
|
|
176
204
|
}
|
|
177
|
-
this.mutableResolvedModules.push(module);
|
|
178
205
|
// We can't merge these 3 loops into a single one, each operation must run
|
|
179
206
|
// after the last operation ran on the whole map.
|
|
180
207
|
// Loop 1: merge the module records map into the cross-module record map.
|
|
181
208
|
for (const record of module.records) {
|
|
182
209
|
const { key } = record.record;
|
|
183
|
-
this.
|
|
210
|
+
this.registry.recordMap.set(key, record);
|
|
211
|
+
moduleBundle.registry.recordMap.set(key, record);
|
|
184
212
|
const { recordNumber } = record.record;
|
|
185
213
|
if (recordNumber != null && !modulePath.startsWith("@")) {
|
|
186
|
-
|
|
187
|
-
if (existing === undefined) {
|
|
188
|
-
this.numberToRecord.set(recordNumber, key);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
const otherRecord = this.recordMap.get(existing);
|
|
192
|
-
const otherRecordName = otherRecord.record.name.text;
|
|
193
|
-
const otherModulePath = otherRecord.modulePath;
|
|
194
|
-
errors.push({
|
|
195
|
-
token: record.record.name,
|
|
196
|
-
message: `Same number as ${otherRecordName} in ${otherModulePath}`,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
214
|
+
moduleBundle.registry.pushNumberRecord(recordNumber, key);
|
|
199
215
|
}
|
|
200
216
|
}
|
|
201
217
|
// Loop 2: resolve every field type of every record in the module.
|
|
202
218
|
// Store the result in the Field object.
|
|
203
219
|
const usedImports = new Set();
|
|
204
|
-
const typeResolver = new TypeResolver(module, this.
|
|
220
|
+
const typeResolver = new TypeResolver(module, this.moduleBundles, usedImports, errors);
|
|
205
221
|
for (const record of module.records) {
|
|
206
222
|
this.storeResolvedFieldTypes(record, typeResolver);
|
|
207
223
|
}
|
|
@@ -247,21 +263,8 @@ export class ModuleSet {
|
|
|
247
263
|
this.validateArrayKeys(responseType, errors);
|
|
248
264
|
}
|
|
249
265
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const existing = this.numberToMethod.get(number);
|
|
253
|
-
if (existing === undefined) {
|
|
254
|
-
this.numberToMethod.set(number, method);
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
const otherMethodName = existing.name.text;
|
|
258
|
-
const otherModulePath = existing.name.line.modulePath;
|
|
259
|
-
errors.push({
|
|
260
|
-
token: method.name,
|
|
261
|
-
message: `Same number as ${otherMethodName} in ${otherModulePath}`,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
266
|
+
const { number } = method;
|
|
267
|
+
moduleBundle.registry.pushNumberMethod(number, method);
|
|
265
268
|
// Resolve the references in the doc comments of the method.
|
|
266
269
|
this.resolveDocReferences(method, module, errors);
|
|
267
270
|
}
|
|
@@ -279,7 +282,8 @@ export class ModuleSet {
|
|
|
279
282
|
this.resolveDocReferences(constant, module, errors);
|
|
280
283
|
}
|
|
281
284
|
ensureAllImportsAreUsed(module, usedImports, errors);
|
|
282
|
-
|
|
285
|
+
this.registry.mergeFrom(moduleBundle.registry);
|
|
286
|
+
return moduleBundle;
|
|
283
287
|
}
|
|
284
288
|
storeResolvedFieldTypes(record, typeResolver) {
|
|
285
289
|
for (const field of record.record.fields) {
|
|
@@ -776,8 +780,8 @@ export class ModuleSet {
|
|
|
776
780
|
if (!resolvedModulePath) {
|
|
777
781
|
return false;
|
|
778
782
|
}
|
|
779
|
-
const importedModule = this.
|
|
780
|
-
if (!importedModule
|
|
783
|
+
const importedModule = this.moduleBundles.get(resolvedModulePath);
|
|
784
|
+
if (!importedModule) {
|
|
781
785
|
return false;
|
|
782
786
|
}
|
|
783
787
|
let newNameChain;
|
|
@@ -788,7 +792,7 @@ export class ModuleSet {
|
|
|
788
792
|
firstName.declaration = match;
|
|
789
793
|
newNameChain = nameParts.slice(1);
|
|
790
794
|
}
|
|
791
|
-
return tryResolveReference(ref, newNameChain, importedModule.result);
|
|
795
|
+
return tryResolveReference(ref, newNameChain, importedModule.module.result);
|
|
792
796
|
}
|
|
793
797
|
case "constant":
|
|
794
798
|
case "field":
|
|
@@ -801,7 +805,7 @@ export class ModuleSet {
|
|
|
801
805
|
}
|
|
802
806
|
}
|
|
803
807
|
};
|
|
804
|
-
const { recordMap } = this;
|
|
808
|
+
const { recordMap } = this.registry;
|
|
805
809
|
// Build list of naming scopes to search, in order of priority.
|
|
806
810
|
const scopes = [];
|
|
807
811
|
const pushRecordAncestorsToScopes = (record) => {
|
|
@@ -867,43 +871,87 @@ export class ModuleSet {
|
|
|
867
871
|
}
|
|
868
872
|
}
|
|
869
873
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
874
|
+
finalize() {
|
|
875
|
+
const modules = new Map();
|
|
876
|
+
for (const [modulePath, moduleBundle] of this.moduleBundles) {
|
|
877
|
+
const { module, tokens } = moduleBundle;
|
|
878
|
+
const moduleErrors = tokens.errors.length ? tokens.errors : module.errors;
|
|
879
|
+
modules.set(modulePath, {
|
|
880
|
+
result: module.result,
|
|
881
|
+
errors: [...moduleErrors],
|
|
882
|
+
});
|
|
873
883
|
}
|
|
874
|
-
|
|
875
|
-
|
|
884
|
+
// Look for duplicate method numbers.
|
|
885
|
+
for (const methods of this.registry.numberToMethods.values()) {
|
|
886
|
+
if (methods.length <= 1) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
const pushError = (method, other) => {
|
|
890
|
+
const modulePath = method.name.line.modulePath;
|
|
891
|
+
const moduleResult = modules.get(modulePath);
|
|
892
|
+
const otherMethodName = other.name.text;
|
|
893
|
+
const otherModulePath = other.name.line.modulePath;
|
|
894
|
+
moduleResult.errors.push({
|
|
895
|
+
token: method.name,
|
|
896
|
+
message: `Same number as ${otherMethodName} in ${otherModulePath}`,
|
|
897
|
+
});
|
|
898
|
+
};
|
|
899
|
+
pushError(methods[0], methods[1]);
|
|
900
|
+
for (let i = 1; i < methods.length; ++i) {
|
|
901
|
+
pushError(methods[i], methods[0]);
|
|
902
|
+
}
|
|
876
903
|
}
|
|
877
|
-
|
|
878
|
-
for (const
|
|
879
|
-
|
|
904
|
+
// Look for duplicate record numbers.
|
|
905
|
+
for (const records of this.registry.numberToRecords.values()) {
|
|
906
|
+
if (records.length <= 1) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const pushError = (recordKey, otherKey) => {
|
|
910
|
+
const record = this.registry.recordMap.get(recordKey);
|
|
911
|
+
const other = this.registry.recordMap.get(otherKey);
|
|
912
|
+
const modulePath = record.record.name.line.modulePath;
|
|
913
|
+
const moduleResult = modules.get(modulePath);
|
|
914
|
+
const otherRecordName = other.record.name.text;
|
|
915
|
+
const otherModulePath = other.modulePath;
|
|
916
|
+
moduleResult.errors.push({
|
|
917
|
+
token: record.record.name,
|
|
918
|
+
message: `Same number as ${otherRecordName} in ${otherModulePath}`,
|
|
919
|
+
});
|
|
920
|
+
};
|
|
921
|
+
pushError(records[0], records[1]);
|
|
922
|
+
for (let i = 1; i < records.length; ++i) {
|
|
923
|
+
pushError(records[i], records[0]);
|
|
924
|
+
}
|
|
880
925
|
}
|
|
881
|
-
|
|
882
|
-
|
|
926
|
+
// Aggregate errors across all modules.
|
|
927
|
+
const errors = [];
|
|
928
|
+
for (const moduleBundle of modules.values()) {
|
|
929
|
+
errors.push(...moduleBundle.errors);
|
|
883
930
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}
|
|
889
|
-
get recordMap() {
|
|
890
|
-
return this.mutableRecordMap;
|
|
891
|
-
}
|
|
892
|
-
get resolvedModules() {
|
|
893
|
-
return this.mutableResolvedModules;
|
|
931
|
+
return {
|
|
932
|
+
modules: modules,
|
|
933
|
+
errors: errors,
|
|
934
|
+
};
|
|
894
935
|
}
|
|
936
|
+
// END PROPERTIES
|
|
895
937
|
findRecordByNumber(recordNumber) {
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
return this.recordMap.get(recordKey);
|
|
938
|
+
const { numberToRecords, recordMap } = this.registry;
|
|
939
|
+
const recordKeys = numberToRecords.get(recordNumber);
|
|
940
|
+
return recordKeys?.length === 1 ? recordMap.get(recordKeys[0]) : undefined;
|
|
901
941
|
}
|
|
902
942
|
findMethodByNumber(methodNumber) {
|
|
903
|
-
|
|
943
|
+
const { numberToMethods } = this.registry;
|
|
944
|
+
const methods = numberToMethods.get(methodNumber);
|
|
945
|
+
return methods?.length === 1 ? methods[0] : undefined;
|
|
946
|
+
}
|
|
947
|
+
get recordMap() {
|
|
948
|
+
return this.registry.recordMap;
|
|
904
949
|
}
|
|
905
950
|
get errors() {
|
|
906
|
-
return this.
|
|
951
|
+
return this.finalizationResult.errors;
|
|
952
|
+
}
|
|
953
|
+
get modules() {
|
|
954
|
+
return this.finalizationResult.modules;
|
|
907
955
|
}
|
|
908
956
|
}
|
|
909
957
|
/**
|
|
@@ -984,9 +1032,9 @@ function validateKeyedItems(items, fieldPath, errors) {
|
|
|
984
1032
|
}
|
|
985
1033
|
}
|
|
986
1034
|
class TypeResolver {
|
|
987
|
-
constructor(module,
|
|
1035
|
+
constructor(module, moduleBundles, usedImports, errors) {
|
|
988
1036
|
this.module = module;
|
|
989
|
-
this.
|
|
1037
|
+
this.moduleBundles = moduleBundles;
|
|
990
1038
|
this.usedImports = usedImports;
|
|
991
1039
|
this.errors = errors;
|
|
992
1040
|
}
|
|
@@ -1023,7 +1071,7 @@ class TypeResolver {
|
|
|
1023
1071
|
// reference, or the module if the record reference is absolute (starts with
|
|
1024
1072
|
// a dot).
|
|
1025
1073
|
let start;
|
|
1026
|
-
const { errors, module, modules, usedImports } = this;
|
|
1074
|
+
const { errors, module, moduleBundles: modules, usedImports } = this;
|
|
1027
1075
|
if (recordOrigin !== "top-level") {
|
|
1028
1076
|
if (!recordRef.absolute) {
|
|
1029
1077
|
// Traverse the chain of ancestors from most nested to top-level.
|
|
@@ -1079,12 +1127,12 @@ class TypeResolver {
|
|
|
1079
1127
|
return undefined;
|
|
1080
1128
|
}
|
|
1081
1129
|
const newModuleResult = modules.get(newModulePath);
|
|
1082
|
-
if (newModuleResult
|
|
1130
|
+
if (!newModuleResult) {
|
|
1083
1131
|
// The module was not found or has errors: an error was already
|
|
1084
1132
|
// registered, no need to register a new one.
|
|
1085
1133
|
return undefined;
|
|
1086
1134
|
}
|
|
1087
|
-
const newModule = newModuleResult.result;
|
|
1135
|
+
const newModule = newModuleResult.module.result;
|
|
1088
1136
|
if (newIt.kind === "import") {
|
|
1089
1137
|
newIt = newModule.nameToDeclaration[name];
|
|
1090
1138
|
if (!newIt) {
|
|
@@ -1123,6 +1171,111 @@ class TypeResolver {
|
|
|
1123
1171
|
};
|
|
1124
1172
|
}
|
|
1125
1173
|
}
|
|
1174
|
+
class Cache {
|
|
1175
|
+
constructor(modulePathToNewContent, modulePathToOldBundle) {
|
|
1176
|
+
this.modulePathToOldBundle = modulePathToOldBundle;
|
|
1177
|
+
this.modulePathToCacheResult = new Map();
|
|
1178
|
+
const unchangedModulePaths = new Set();
|
|
1179
|
+
for (const [modulePath, newContent] of modulePathToNewContent) {
|
|
1180
|
+
const oldBundle = modulePathToOldBundle.get(modulePath);
|
|
1181
|
+
if (oldBundle?.tokens.result.sourceCode === newContent) {
|
|
1182
|
+
unchangedModulePaths.add(modulePath);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const classify = (modulePath) => {
|
|
1186
|
+
const oldBundle = modulePathToOldBundle.get(modulePath);
|
|
1187
|
+
if (!oldBundle) {
|
|
1188
|
+
return { kind: "no-cache" };
|
|
1189
|
+
}
|
|
1190
|
+
{
|
|
1191
|
+
const inMap = this.modulePathToCacheResult.get(modulePath);
|
|
1192
|
+
if (inMap) {
|
|
1193
|
+
return inMap;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
let result;
|
|
1197
|
+
const newContent = modulePathToNewContent.get(modulePath);
|
|
1198
|
+
if (newContent === oldBundle.tokens.result.sourceCode) {
|
|
1199
|
+
// Assume best case, may downgrade later.
|
|
1200
|
+
this.modulePathToCacheResult.set(modulePath, {
|
|
1201
|
+
kind: "module-bundle",
|
|
1202
|
+
bundle: oldBundle,
|
|
1203
|
+
});
|
|
1204
|
+
const directDependencies = Object.keys(oldBundle.module.result?.pathToImportedNames);
|
|
1205
|
+
result = directDependencies.every((dep) => classify(dep).kind === "module-bundle")
|
|
1206
|
+
? { kind: "module-bundle", bundle: oldBundle }
|
|
1207
|
+
: { kind: "module-tokens", tokens: oldBundle.tokens };
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
result = { kind: "no-cache" };
|
|
1211
|
+
}
|
|
1212
|
+
this.modulePathToCacheResult.set(modulePath, result);
|
|
1213
|
+
return result;
|
|
1214
|
+
};
|
|
1215
|
+
for (const modulePath of modulePathToOldBundle.keys()) {
|
|
1216
|
+
classify(modulePath);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
getModuleCacheResult(modulePath) {
|
|
1220
|
+
return this.modulePathToCacheResult.get(modulePath) ?? { kind: "no-cache" };
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/** Registry of declarations possibly across multiple modules. */
|
|
1224
|
+
class DeclarationRegistry {
|
|
1225
|
+
constructor() {
|
|
1226
|
+
this.recordMap = new Map();
|
|
1227
|
+
this.numberToRecords = new Map();
|
|
1228
|
+
this.numberToMethods = new Map();
|
|
1229
|
+
}
|
|
1230
|
+
mergeFrom(other) {
|
|
1231
|
+
for (const [key, value] of other.recordMap) {
|
|
1232
|
+
this.recordMap.set(key, value);
|
|
1233
|
+
}
|
|
1234
|
+
for (const [number, value] of other.numberToRecords) {
|
|
1235
|
+
let existing = this.numberToRecords.get(number);
|
|
1236
|
+
if (!existing) {
|
|
1237
|
+
existing = [];
|
|
1238
|
+
this.numberToRecords.set(number, existing);
|
|
1239
|
+
}
|
|
1240
|
+
existing.push(...value);
|
|
1241
|
+
}
|
|
1242
|
+
for (const [number, value] of other.numberToMethods) {
|
|
1243
|
+
let existing = this.numberToMethods.get(number);
|
|
1244
|
+
if (!existing) {
|
|
1245
|
+
existing = [];
|
|
1246
|
+
this.numberToMethods.set(number, existing);
|
|
1247
|
+
}
|
|
1248
|
+
existing.push(...value);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
pushNumberRecord(number, record) {
|
|
1252
|
+
let value = this.numberToRecords.get(number);
|
|
1253
|
+
if (!value) {
|
|
1254
|
+
value = [];
|
|
1255
|
+
this.numberToRecords.set(number, value);
|
|
1256
|
+
}
|
|
1257
|
+
value.push(record);
|
|
1258
|
+
}
|
|
1259
|
+
pushNumberMethod(number, method) {
|
|
1260
|
+
let value = this.numberToMethods.get(number);
|
|
1261
|
+
if (!value) {
|
|
1262
|
+
value = [];
|
|
1263
|
+
this.numberToMethods.set(number, value);
|
|
1264
|
+
}
|
|
1265
|
+
value.push(method);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
class ModuleBundle {
|
|
1269
|
+
constructor(tokens, module) {
|
|
1270
|
+
this.tokens = tokens;
|
|
1271
|
+
this.module = module;
|
|
1272
|
+
/**
|
|
1273
|
+
* Registry of declarations found in this module only.
|
|
1274
|
+
* Will be merged into the "global" registry.
|
|
1275
|
+
*/
|
|
1276
|
+
this.registry = new DeclarationRegistry();
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1126
1279
|
function ensureAllImportsAreUsed(module, usedImports, errors) {
|
|
1127
1280
|
for (const declaration of module.declarations) {
|
|
1128
1281
|
if (declaration.kind === "import") {
|
|
@@ -1145,43 +1298,82 @@ function ensureAllImportsAreUsed(module, usedImports, errors) {
|
|
|
1145
1298
|
}
|
|
1146
1299
|
}
|
|
1147
1300
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1301
|
+
/**
|
|
1302
|
+
* Returns suggested module paths for import auto-completion.
|
|
1303
|
+
*
|
|
1304
|
+
* Given the partial path the user has typed, returns suggestions from
|
|
1305
|
+
* `modulePathToContent`. For paths where there is more after the matched
|
|
1306
|
+
* segment, the suggestion is truncated at the next "/" and a trailing "/" is
|
|
1307
|
+
* appended to signal that it is a directory, not a file.
|
|
1308
|
+
*
|
|
1309
|
+
* Handles both absolute paths (e.g. "bb/ee") and relative paths (e.g.
|
|
1310
|
+
* "./other", "../sibling").
|
|
1311
|
+
*/
|
|
1312
|
+
function suggestModulePaths(typedPath, originModulePath, modulePathToContent) {
|
|
1313
|
+
const isRelative = typedPath.startsWith("./") || typedPath.startsWith("../");
|
|
1314
|
+
// Compute the absolute path prefix to match against all module paths.
|
|
1315
|
+
let absolutePrefix;
|
|
1316
|
+
if (isRelative) {
|
|
1317
|
+
// Split at the last "/" so that the directory component (fully typed) can
|
|
1318
|
+
// be resolved cleanly, while the tail (partial filename/dir prefix) is
|
|
1319
|
+
// appended afterwards.
|
|
1320
|
+
const lastSlash = typedPath.lastIndexOf("/");
|
|
1321
|
+
const dirComponent = typedPath.slice(0, lastSlash + 1);
|
|
1322
|
+
const filePrefix = typedPath.slice(lastSlash + 1);
|
|
1323
|
+
// Append a dummy filename so Paths.join/normalize treat the directory
|
|
1324
|
+
// component as a file path (avoids trailing-slash edge-cases), then strip
|
|
1325
|
+
// it off again.
|
|
1326
|
+
const dummy = dirComponent + "__dummy__";
|
|
1327
|
+
const resolvedDummy = Paths.join(Paths.dirname(originModulePath), dummy.startsWith("./") ? dummy.slice(2) : dummy).replace(/\\/g, "/");
|
|
1328
|
+
const absoluteDir = resolvedDummy.slice(0, resolvedDummy.length - "__dummy__".length);
|
|
1329
|
+
absolutePrefix = absoluteDir + filePrefix;
|
|
1165
1330
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
super();
|
|
1170
|
-
this.fileReader = fileReader;
|
|
1171
|
-
this.rootPath = rootPath;
|
|
1331
|
+
else if (originModulePath.startsWith("@") && !typedPath.startsWith("@")) {
|
|
1332
|
+
// Mirror the package-prefix logic from resolveModulePath.
|
|
1333
|
+
absolutePrefix = extractPackagePrefix(originModulePath) + typedPath;
|
|
1172
1334
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1335
|
+
else {
|
|
1336
|
+
absolutePrefix = typedPath;
|
|
1175
1337
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
this.moduleMap = moduleMap;
|
|
1338
|
+
absolutePrefix = absolutePrefix.replace(/\\/g, "/");
|
|
1339
|
+
if (absolutePrefix.startsWith("../")) {
|
|
1340
|
+
// Typed path escapes the root; no valid suggestions.
|
|
1341
|
+
return [];
|
|
1181
1342
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1343
|
+
const originBaseDir = Paths.dirname(originModulePath).replace(/\\/g, "/");
|
|
1344
|
+
const suggestions = new Set();
|
|
1345
|
+
for (const path of modulePathToContent.keys()) {
|
|
1346
|
+
if (!path.startsWith(absolutePrefix))
|
|
1347
|
+
continue;
|
|
1348
|
+
const remaining = path.slice(absolutePrefix.length);
|
|
1349
|
+
const slashIndex = remaining.indexOf("/");
|
|
1350
|
+
// If there is a sub-path after the matched prefix, collapse to the next
|
|
1351
|
+
// directory segment and append "/". Otherwise use the full path.
|
|
1352
|
+
const absoluteSuggestion = slashIndex >= 0
|
|
1353
|
+
? absolutePrefix + remaining.slice(0, slashIndex + 1)
|
|
1354
|
+
: path;
|
|
1355
|
+
if (isRelative) {
|
|
1356
|
+
const isDir = absoluteSuggestion.endsWith("/");
|
|
1357
|
+
const absPath = isDir
|
|
1358
|
+
? absoluteSuggestion.slice(0, -1)
|
|
1359
|
+
: absoluteSuggestion;
|
|
1360
|
+
let rel = Paths.relative(originBaseDir, absPath).replace(/\\/g, "/");
|
|
1361
|
+
// Paths.relative returns "" when both paths are the same; normalise to
|
|
1362
|
+
// "." so the subsequent prefix-check and directory-slash logic work
|
|
1363
|
+
// correctly (avoids producing the invalid path ".//" for same-dir cases).
|
|
1364
|
+
if (rel === "") {
|
|
1365
|
+
rel = ".";
|
|
1366
|
+
}
|
|
1367
|
+
if (!rel.startsWith(".")) {
|
|
1368
|
+
rel = "./" + rel;
|
|
1369
|
+
}
|
|
1370
|
+
suggestions.add(isDir ? rel + "/" : rel);
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
suggestions.add(absoluteSuggestion);
|
|
1374
|
+
}
|
|
1184
1375
|
}
|
|
1376
|
+
return [...suggestions].map((name) => ({ name }));
|
|
1185
1377
|
}
|
|
1186
1378
|
function resolveModulePath(pathToken, originModulePath, errors) {
|
|
1187
1379
|
let modulePath = unquoteAndUnescape(pathToken.text);
|