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