UNPKG

12.1 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.DependencyResolverFactory = void 0;
4const tslib_1 = require("tslib");
5const inversify_1 = require("inversify");
6const ts = require("typescript");
7const tsutils_1 = require("tsutils");
8const utils_1 = require("../utils");
9const bind_decorator_1 = require("bind-decorator");
10const path = require("path");
11let DependencyResolverFactory = class DependencyResolverFactory {
12 create(host, program) {
13 return new DependencyResolverImpl(host, program);
14 }
15};
16DependencyResolverFactory = tslib_1.__decorate([
17 inversify_1.injectable()
18], DependencyResolverFactory);
19exports.DependencyResolverFactory = DependencyResolverFactory;
20class DependencyResolverImpl {
21 constructor(host, program) {
22 var _a, _b;
23 this.host = host;
24 this.program = program;
25 this.dependencies = new Map();
26 this.fileToProjectReference = undefined;
27 this.fileMetadata = new Map();
28 this.compilerOptions = this.program.getCompilerOptions();
29 this.useSourceOfProjectReferenceRedirect = ((_b = (_a = this.host).useSourceOfProjectReferenceRedirect) === null || _b === void 0 ? void 0 : _b.call(_a)) === true &&
30 !this.compilerOptions.disableSourceOfProjectReferenceRedirect;
31 this.state = undefined;
32 }
33 update(program, updatedFile) {
34 this.state = undefined;
35 this.dependencies.delete(updatedFile);
36 this.fileMetadata.delete(updatedFile);
37 this.program = program;
38 }
39 buildState() {
40 const affectsGlobalScope = [];
41 const ambientModules = new Map();
42 const patternAmbientModules = new Map();
43 const moduleAugmentationsTemp = new Map();
44 for (const file of this.program.getSourceFiles()) {
45 const meta = this.getFileMetaData(file.fileName);
46 if (meta.affectsGlobalScope)
47 affectsGlobalScope.push(file.fileName);
48 for (const ambientModule of meta.ambientModules) {
49 const map = meta.isExternalModule
50 ? moduleAugmentationsTemp
51 : ambientModule.includes('*')
52 ? patternAmbientModules
53 : ambientModules;
54 addToList(map, ambientModule, file.fileName);
55 }
56 }
57 const moduleAugmentations = new Map();
58 for (const [module, files] of moduleAugmentationsTemp) {
59 // if an ambient module with the same identifier exists, the augmentation always applies to that
60 const ambientModuleAffectingFiles = ambientModules.get(module);
61 if (ambientModuleAffectingFiles !== undefined) {
62 ambientModuleAffectingFiles.push(...files);
63 continue;
64 }
65 for (const file of files) {
66 const resolved = this.getExternalReferences(file).get(module);
67 // if an augmentation's identifier can be resolved from the declaring file, the augmentation applies to the resolved path
68 if (resolved != null) { // tslint:disable-line:triple-equals
69 addToList(moduleAugmentations, resolved, file);
70 }
71 else {
72 // if a pattern ambient module matches the augmented identifier, the augmentation applies to that
73 const matchingPattern = getBestMatchingPattern(module, patternAmbientModules.keys());
74 if (matchingPattern !== undefined)
75 addToList(patternAmbientModules, matchingPattern, file);
76 }
77 }
78 }
79 return {
80 affectsGlobalScope,
81 ambientModules,
82 moduleAugmentations,
83 patternAmbientModules,
84 };
85 }
86 getFilesAffectingGlobalScope() {
87 var _a;
88 return ((_a = this.state) !== null && _a !== void 0 ? _a : (this.state = this.buildState())).affectsGlobalScope;
89 }
90 getDependencies(file) {
91 var _a;
92 const result = new Map();
93 if (this.program.getSourceFile(file) === undefined)
94 return result;
95 (_a = this.state) !== null && _a !== void 0 ? _a : (this.state = this.buildState());
96 {
97 const augmentations = this.state.moduleAugmentations.get(file);
98 if (augmentations !== undefined)
99 result.set('\0', augmentations);
100 }
101 for (const [identifier, resolved] of this.getExternalReferences(file)) {
102 const filesAffectingAmbientModule = this.state.ambientModules.get(identifier);
103 if (filesAffectingAmbientModule !== undefined) {
104 result.set(identifier, filesAffectingAmbientModule);
105 }
106 else if (resolved !== null) {
107 const list = [resolved];
108 const augmentations = this.state.moduleAugmentations.get(resolved);
109 if (augmentations !== undefined)
110 list.push(...augmentations);
111 result.set(identifier, list);
112 }
113 else {
114 const pattern = getBestMatchingPattern(identifier, this.state.patternAmbientModules.keys());
115 if (pattern !== undefined) {
116 result.set(identifier, this.state.patternAmbientModules.get(pattern));
117 }
118 else {
119 result.set(identifier, null);
120 }
121 }
122 }
123 const meta = this.fileMetadata.get(file);
124 if (!meta.isExternalModule)
125 for (const ambientModule of meta.ambientModules)
126 result.set(ambientModule, this.state[ambientModule.includes('*') ? 'patternAmbientModules' : 'ambientModules'].get(ambientModule));
127 return result;
128 }
129 getFileMetaData(fileName) {
130 return utils_1.resolveCachedResult(this.fileMetadata, fileName, this.collectMetaDataForFile);
131 }
132 getExternalReferences(fileName) {
133 return utils_1.resolveCachedResult(this.dependencies, fileName, this.collectExternalReferences);
134 }
135 collectMetaDataForFile(fileName) {
136 return collectFileMetadata(this.program.getSourceFile(fileName));
137 }
138 collectExternalReferences(fileName) {
139 var _a;
140 // TODO add tslib if importHelpers is enabled
141 const sourceFile = this.program.getSourceFile(fileName);
142 const references = new Set(tsutils_1.findImports(sourceFile, tsutils_1.ImportKind.All, false).map(({ text }) => text));
143 if (ts.isExternalModule(sourceFile))
144 for (const augmentation of this.getFileMetaData(fileName).ambientModules)
145 references.add(augmentation);
146 const result = new Map();
147 if (references.size === 0)
148 return result;
149 (_a = this.fileToProjectReference) !== null && _a !== void 0 ? _a : (this.fileToProjectReference = createProjectReferenceMap(this.program.getResolvedProjectReferences()));
150 const arr = Array.from(references);
151 const resolved = this.host.resolveModuleNames(arr, fileName, undefined, this.fileToProjectReference.get(fileName), this.compilerOptions);
152 for (let i = 0; i < resolved.length; ++i) {
153 const current = resolved[i];
154 if (current === undefined) {
155 result.set(arr[i], null);
156 }
157 else {
158 const projectReference = this.useSourceOfProjectReferenceRedirect
159 ? this.fileToProjectReference.get(current.resolvedFileName)
160 : undefined;
161 if (projectReference === undefined) {
162 result.set(arr[i], current.resolvedFileName);
163 }
164 else if (projectReference.commandLine.options.outFile) {
165 // with outFile the files must be global anyway, so we don't care about the exact file
166 result.set(arr[i], projectReference.commandLine.fileNames[0]);
167 }
168 else {
169 result.set(arr[i], getSourceOfProjectReferenceRedirect(current.resolvedFileName, projectReference));
170 }
171 }
172 }
173 return result;
174 }
175}
176tslib_1.__decorate([
177 bind_decorator_1.default,
178 tslib_1.__metadata("design:type", Function),
179 tslib_1.__metadata("design:paramtypes", [String]),
180 tslib_1.__metadata("design:returntype", void 0)
181], DependencyResolverImpl.prototype, "collectMetaDataForFile", null);
182tslib_1.__decorate([
183 bind_decorator_1.default,
184 tslib_1.__metadata("design:type", Function),
185 tslib_1.__metadata("design:paramtypes", [String]),
186 tslib_1.__metadata("design:returntype", Map)
187], DependencyResolverImpl.prototype, "collectExternalReferences", null);
188function getBestMatchingPattern(moduleName, patternAmbientModules) {
189 // TypeScript uses the pattern with the longest matching prefix
190 let longestMatchLength = -1;
191 let longestMatch;
192 for (const pattern of patternAmbientModules) {
193 if (moduleName.length < pattern.length - 1)
194 continue; // compare length without the wildcard first, to avoid false positives like 'foo' matching 'foo*oo'
195 const index = pattern.indexOf('*');
196 if (index > longestMatchLength &&
197 moduleName.startsWith(pattern.substring(0, index)) &&
198 moduleName.endsWith(pattern.substring(index + 1))) {
199 longestMatchLength = index;
200 longestMatch = pattern;
201 }
202 }
203 return longestMatch;
204}
205function createProjectReferenceMap(references) {
206 const result = new Map();
207 for (const ref of utils_1.iterateProjectReferences(references))
208 for (const file of utils_1.getOutputFileNamesOfProjectReference(ref))
209 result.set(file, ref);
210 return result;
211}
212function addToList(map, key, value) {
213 const arr = map.get(key);
214 if (arr === undefined) {
215 map.set(key, [value]);
216 }
217 else {
218 arr.push(value);
219 }
220}
221function collectFileMetadata(sourceFile) {
222 let affectsGlobalScope;
223 const ambientModules = new Set();
224 const isExternalModule = ts.isExternalModule(sourceFile);
225 for (const statement of sourceFile.statements) {
226 if (statement.flags & ts.NodeFlags.GlobalAugmentation) {
227 affectsGlobalScope = true;
228 }
229 else if (tsutils_1.isModuleDeclaration(statement) && statement.name.kind === ts.SyntaxKind.StringLiteral) {
230 ambientModules.add(statement.name.text);
231 if (!isExternalModule && !affectsGlobalScope && statement.body !== undefined) {
232 // search for global augmentations in ambient module blocks
233 for (const s of statement.body.statements) {
234 if (s.flags & ts.NodeFlags.GlobalAugmentation) {
235 affectsGlobalScope = true;
236 break;
237 }
238 }
239 }
240 }
241 else if (tsutils_1.isNamespaceExportDeclaration(statement)) {
242 affectsGlobalScope = true;
243 }
244 else if (affectsGlobalScope === undefined) { // files that only consist of ambient modules do not affect global scope
245 affectsGlobalScope = !isExternalModule;
246 }
247 }
248 return { ambientModules, isExternalModule, affectsGlobalScope: affectsGlobalScope === true };
249}
250function getSourceOfProjectReferenceRedirect(outputFileName, ref) {
251 const options = ref.commandLine.options;
252 const projectDirectory = path.dirname(ref.sourceFile.fileName);
253 const origin = utils_1.unixifyPath(path.resolve(options.rootDir || projectDirectory, path.relative(options.declarationDir || options.outDir || /* istanbul ignore next */ projectDirectory, outputFileName.slice(0, -5))));
254 for (const extension of ['.ts', '.tsx', '.js', '.jsx']) {
255 const name = origin + extension;
256 if (ref.commandLine.fileNames.includes(name))
257 return name;
258 }
259 /* istanbul ignore next */
260 return outputFileName; // should never happen
261}
262//# sourceMappingURL=dependency-resolver.js.map
\No newline at end of file