UNPKG

14.1 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
5 * This code may only be used under the BSD style license found at
6 * http://polymer.github.io/LICENSE.txt
7 * The complete set of authors may be found at
8 * http://polymer.github.io/AUTHORS.txt
9 * The complete set of contributors may be found at
10 * http://polymer.github.io/CONTRIBUTORS.txt
11 * Code distributed by Google as part of the polymer project is also
12 * subject to an additional IP rights grant found at
13 * http://polymer.github.io/PATENTS.txt
14 */
15Object.defineProperty(exports, "__esModule", { value: true });
16const analysis_1 = require("./analysis");
17const immutable_1 = require("./immutable");
18const map_1 = require("./map");
19const resolvable_1 = require("./resolvable");
20/**
21 * The metadata for all features and elements defined in one document
22 */
23class ScannedDocument {
24 constructor(document, features, warnings = []) {
25 this.isInline = false;
26 this.document = document;
27 this.features = features;
28 this.warnings = warnings;
29 this.isInline = document.isInline;
30 }
31 get sourceRange() {
32 return this.document.sourceRange;
33 }
34 get astNode() {
35 return this.document.astNode;
36 }
37 get url() {
38 return this.document.url;
39 }
40 /**
41 * Gets all features in this scanned document and all inline documents it
42 * contains.
43 */
44 getNestedFeatures() {
45 const result = [];
46 this._getNestedFeatures(result);
47 return result;
48 }
49 _getNestedFeatures(features) {
50 for (const feature of this.features) {
51 // Ad hoc test needed here to avoid a problematic import loop.
52 const maybeInlineDoc = feature;
53 if (maybeInlineDoc.constructor.name === 'ScannedInlineDocument' &&
54 maybeInlineDoc.scannedDocument) {
55 maybeInlineDoc.scannedDocument._getNestedFeatures(features);
56 }
57 else {
58 features.push(feature);
59 }
60 }
61 }
62}
63exports.ScannedDocument = ScannedDocument;
64function isDeclaredWithStatement(feature) {
65 return feature.statementAst !== undefined;
66}
67class Document {
68 constructor(base, analyzer, languageAnalysis) {
69 this.kinds = new Set(['document']);
70 this.identifiers = new Set();
71 this._localFeatures = new Set();
72 this._localFeaturesByStatement = new map_1.MapWithDefault(() => new Set());
73 /**
74 * To handle recursive dependency graphs we must track whether we've started
75 * resolving this Document so that we can reliably early exit even if one
76 * of our dependencies tries to resolve this document.
77 */
78 this._begunResolving = false;
79 /**
80 * True after this document and all of its children are finished resolving.
81 */
82 this._doneResolving = false;
83 this._featuresByKind = null;
84 this._featuresByKindAndId = null;
85 if (base == null) {
86 throw new Error('base is null');
87 }
88 if (analyzer == null) {
89 throw new Error('analyzer is null');
90 }
91 this._scannedDocument = base;
92 this._analysisContext = analyzer;
93 this.languageAnalysis = languageAnalysis;
94 if (!base.isInline) {
95 immutable_1.unsafeAsMutable(this.identifiers).add(this.url);
96 }
97 immutable_1.unsafeAsMutable(this.kinds).add(`${this.parsedDocument.type}-document`);
98 this.warnings = Array.from(base.warnings);
99 }
100 get url() {
101 return this._scannedDocument.url;
102 }
103 get isInline() {
104 return this._scannedDocument.isInline;
105 }
106 get sourceRange() {
107 return this._scannedDocument.sourceRange;
108 }
109 get astNode() {
110 return this._scannedDocument.astNode;
111 }
112 get parsedDocument() {
113 return this._scannedDocument.document;
114 }
115 get resolved() {
116 return this._doneResolving;
117 }
118 get type() {
119 return this.parsedDocument.type;
120 }
121 /**
122 * Resolves all features of this document, so that they have references to all
123 * their dependencies.
124 *
125 * This method can only be called once
126 */
127 // TODO(justinfagnani): move to ScannedDocument
128 resolve() {
129 if (this._doneResolving) {
130 throw new Error('resolve can only be called once');
131 }
132 if (this._begunResolving) {
133 return;
134 }
135 this._begunResolving = true;
136 this._addFeature(this);
137 for (const scannedFeature of this._scannedDocument.features) {
138 if (resolvable_1.isResolvable(scannedFeature)) {
139 const feature = scannedFeature.resolve(this);
140 if (feature) {
141 this._addFeature(feature);
142 }
143 }
144 }
145 this._doneResolving = true;
146 }
147 /**
148 * Adds and indexes a feature to this document before resolve().
149 */
150 _addFeature(feature) {
151 if (this._doneResolving) {
152 throw new Error('_addFeature can not be called after _resolve()');
153 }
154 this._indexFeature(feature);
155 this._localFeatures.add(feature);
156 if (isDeclaredWithStatement(feature) &&
157 feature.statementAst !== undefined) {
158 this._localFeaturesByStatement.get(feature.statementAst).add(feature);
159 }
160 }
161 getFeatures(query = {}) {
162 if (query.statement !== undefined) {
163 return this._getByStatement(query.statement, query.kind);
164 }
165 if (query.id && query.kind) {
166 return this._getById(query.kind, query.id, query);
167 }
168 else if (query.kind) {
169 return this._getByKind(query.kind, query);
170 }
171 const features = new Set();
172 this._listFeatures(features, new Set(), query);
173 const queryId = query.id;
174 if (queryId) {
175 const filteredFeatures = Array.from(features).filter((f) => f.identifiers.has(queryId));
176 return new Set(filteredFeatures);
177 }
178 return features;
179 }
180 _getByKind(kind, query = {}) {
181 if (this._featuresByKind && this._isCachable(query)) {
182 // We have a fast index! Use that.
183 const features = this._featuresByKind.get(kind) || new Set();
184 if (!query.externalPackages) {
185 return this._filterOutExternal(features);
186 }
187 return features;
188 }
189 else if (this._doneResolving && this._isCachable(query)) {
190 // We're done discovering features in this document and its children so
191 // we can safely build up the indexes.
192 this._buildIndexes();
193 return this._getByKind(kind, query);
194 }
195 return this._getSlowlyByKind(kind, query);
196 }
197 _getById(kind, identifier, query = {}) {
198 if (this._featuresByKindAndId && this._isCachable(query)) {
199 // We have a fast index! Use that.
200 const idMap = this._featuresByKindAndId.get(kind);
201 const features = (idMap && idMap.get(identifier)) || new Set();
202 if (!query.externalPackages) {
203 return this._filterOutExternal(features);
204 }
205 return features;
206 }
207 else if (this._doneResolving && this._isCachable(query)) {
208 // We're done discovering features in this document and its children so
209 // we can safely build up the indexes.
210 this._buildIndexes();
211 return this._getById(kind, identifier, query);
212 }
213 const result = new Set();
214 for (const featureOfKind of this._getByKind(kind, query)) {
215 if (featureOfKind.identifiers.has(identifier)) {
216 result.add(featureOfKind);
217 }
218 }
219 return result;
220 }
221 _getByStatement(statement, kind) {
222 const result = this._localFeaturesByStatement.get(statement);
223 if (kind === undefined) {
224 return result;
225 }
226 return this._filterByKind(result, kind);
227 }
228 /** Filters out the given features by the given kind. */
229 _filterByKind(features, kind) {
230 const result = new Set();
231 for (const feature of features) {
232 if (feature.kinds.has(kind)) {
233 result.add(feature);
234 }
235 }
236 return result;
237 }
238 _isCachable(query = {}) {
239 return !!query.imported && !query.noLazyImports &&
240 !query.excludeBackreferences;
241 }
242 _getSlowlyByKind(kind, query) {
243 const allFeatures = new Set();
244 this._listFeatures(allFeatures, new Set(), query);
245 const result = new Set();
246 for (const feature of allFeatures) {
247 if (feature.kinds.has(kind)) {
248 result.add(feature);
249 }
250 }
251 return result;
252 }
253 /**
254 * Walks the graph of documents, starting from `this`, finding features which
255 * match the given query and adding them to the `result` set. Uses `visited`
256 * to deal with cycles.
257 *
258 * This method is O(numFeatures), though it does not walk documents that are
259 * not relevant to the query (e.g. based on whether the query follows imports,
260 * or excludes lazy imports)
261 */
262 _listFeatures(result, visited, query) {
263 if (visited.has(this)) {
264 return;
265 }
266 visited.add(this);
267 for (const feature of this._localFeatures) {
268 // Don't include a DocumentBackreference feature in the result set if the
269 // query excludes them.
270 if (!feature.kinds.has('document-backreference') ||
271 !query.excludeBackreferences) {
272 result.add(feature);
273 }
274 if (feature.kinds.has('document-backreference') &&
275 !query.excludeBackreferences) {
276 const containerDocument = feature.document;
277 result.add(containerDocument);
278 containerDocument._listFeatures(result, visited, query);
279 }
280 if (feature.kinds.has('document')) {
281 feature._listFeatures(result, visited, query);
282 }
283 if (feature.kinds.has('import') && query.imported) {
284 const imprt = feature;
285 const isPackageInternal = imprt.document && !analysis_1.Analysis.isExternal(imprt.url);
286 const externalityOk = query.externalPackages || isPackageInternal;
287 const lazinessOk = !query.noLazyImports || !imprt.lazy;
288 if (imprt.document !== undefined && externalityOk && lazinessOk) {
289 imprt.document._listFeatures(result, visited, query);
290 }
291 }
292 }
293 }
294 _filterOutExternal(features) {
295 const result = new Set();
296 for (const feature of features) {
297 if (feature.sourceRange &&
298 analysis_1.Analysis.isExternal(feature.sourceRange.file)) {
299 continue;
300 }
301 result.add(feature);
302 }
303 return result;
304 }
305 /**
306 * Get warnings for the document and all matched features.
307 */
308 getWarnings(query = {}) {
309 const warnings = new Set(this.warnings);
310 for (const feature of this.getFeatures(query)) {
311 for (const warning of feature.warnings) {
312 warnings.add(warning);
313 }
314 }
315 return Array.from(warnings);
316 }
317 toString() {
318 return this._toString(new Set()).join('\n');
319 }
320 _toString(documentsWalked) {
321 let result = [`<Document type=${this.parsedDocument.type} url=${this.url}>\n`];
322 if (documentsWalked.has(this)) {
323 return result;
324 }
325 documentsWalked.add(this);
326 for (const localFeature of this._localFeatures) {
327 if (localFeature instanceof Document) {
328 result = result.concat(localFeature._toString(documentsWalked).map((line) => ` ${line}`));
329 }
330 else {
331 let subResult = localFeature.toString();
332 if (subResult === '[object Object]') {
333 subResult = `<${localFeature.constructor.name} kinds="${Array.from(localFeature.kinds).join(', ')}" ids="${Array.from(localFeature.identifiers).join(',')}">}`;
334 }
335 result.push(` ${subResult}`);
336 }
337 }
338 return result;
339 }
340 stringify() {
341 const inlineDocuments = Array.from(this._localFeatures)
342 .filter((f) => f instanceof Document && f.isInline)
343 .map((d) => d.parsedDocument);
344 return this.parsedDocument.stringify({ inlineDocuments: inlineDocuments });
345 }
346 _indexFeature(feature) {
347 if (!this._featuresByKind || !this._featuresByKindAndId) {
348 return;
349 }
350 for (const kind of feature.kinds) {
351 const kindSet = this._featuresByKind.get(kind) || new Set();
352 kindSet.add(feature);
353 this._featuresByKind.set(kind, kindSet);
354 for (const id of feature.identifiers) {
355 const identifiersMap = this._featuresByKindAndId.get(kind) ||
356 new Map();
357 this._featuresByKindAndId.set(kind, identifiersMap);
358 const idSet = identifiersMap.get(id) || new Set();
359 identifiersMap.set(id, idSet);
360 idSet.add(feature);
361 }
362 }
363 }
364 _buildIndexes() {
365 if (this._featuresByKind) {
366 throw new Error('Tried to build indexes multiple times. This should never happen.');
367 }
368 if (!this._doneResolving) {
369 throw new Error(`Tried to build indexes before finished resolving. ` +
370 `Need to wait until afterwards or the indexes would be incomplete.`);
371 }
372 this._featuresByKind = new Map();
373 this._featuresByKindAndId = new Map();
374 for (const feature of this.getFeatures({ imported: true, externalPackages: true })) {
375 this._indexFeature(feature);
376 }
377 }
378}
379exports.Document = Document;
380//# sourceMappingURL=document.js.map
\No newline at end of file