UNPKG

polymer-analyzer

Version:
380 lines 14.1 kB
"use strict"; /** * @license * Copyright (c) 2016 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ Object.defineProperty(exports, "__esModule", { value: true }); const analysis_1 = require("./analysis"); const immutable_1 = require("./immutable"); const map_1 = require("./map"); const resolvable_1 = require("./resolvable"); /** * The metadata for all features and elements defined in one document */ class ScannedDocument { constructor(document, features, warnings = []) { this.isInline = false; this.document = document; this.features = features; this.warnings = warnings; this.isInline = document.isInline; } get sourceRange() { return this.document.sourceRange; } get astNode() { return this.document.astNode; } get url() { return this.document.url; } /** * Gets all features in this scanned document and all inline documents it * contains. */ getNestedFeatures() { const result = []; this._getNestedFeatures(result); return result; } _getNestedFeatures(features) { for (const feature of this.features) { // Ad hoc test needed here to avoid a problematic import loop. const maybeInlineDoc = feature; if (maybeInlineDoc.constructor.name === 'ScannedInlineDocument' && maybeInlineDoc.scannedDocument) { maybeInlineDoc.scannedDocument._getNestedFeatures(features); } else { features.push(feature); } } } } exports.ScannedDocument = ScannedDocument; function isDeclaredWithStatement(feature) { return feature.statementAst !== undefined; } class Document { constructor(base, analyzer, languageAnalysis) { this.kinds = new Set(['document']); this.identifiers = new Set(); this._localFeatures = new Set(); this._localFeaturesByStatement = new map_1.MapWithDefault(() => new Set()); /** * To handle recursive dependency graphs we must track whether we've started * resolving this Document so that we can reliably early exit even if one * of our dependencies tries to resolve this document. */ this._begunResolving = false; /** * True after this document and all of its children are finished resolving. */ this._doneResolving = false; this._featuresByKind = null; this._featuresByKindAndId = null; if (base == null) { throw new Error('base is null'); } if (analyzer == null) { throw new Error('analyzer is null'); } this._scannedDocument = base; this._analysisContext = analyzer; this.languageAnalysis = languageAnalysis; if (!base.isInline) { immutable_1.unsafeAsMutable(this.identifiers).add(this.url); } immutable_1.unsafeAsMutable(this.kinds).add(`${this.parsedDocument.type}-document`); this.warnings = Array.from(base.warnings); } get url() { return this._scannedDocument.url; } get isInline() { return this._scannedDocument.isInline; } get sourceRange() { return this._scannedDocument.sourceRange; } get astNode() { return this._scannedDocument.astNode; } get parsedDocument() { return this._scannedDocument.document; } get resolved() { return this._doneResolving; } get type() { return this.parsedDocument.type; } /** * Resolves all features of this document, so that they have references to all * their dependencies. * * This method can only be called once */ // TODO(justinfagnani): move to ScannedDocument resolve() { if (this._doneResolving) { throw new Error('resolve can only be called once'); } if (this._begunResolving) { return; } this._begunResolving = true; this._addFeature(this); for (const scannedFeature of this._scannedDocument.features) { if (resolvable_1.isResolvable(scannedFeature)) { const feature = scannedFeature.resolve(this); if (feature) { this._addFeature(feature); } } } this._doneResolving = true; } /** * Adds and indexes a feature to this document before resolve(). */ _addFeature(feature) { if (this._doneResolving) { throw new Error('_addFeature can not be called after _resolve()'); } this._indexFeature(feature); this._localFeatures.add(feature); if (isDeclaredWithStatement(feature) && feature.statementAst !== undefined) { this._localFeaturesByStatement.get(feature.statementAst).add(feature); } } getFeatures(query = {}) { if (query.statement !== undefined) { return this._getByStatement(query.statement, query.kind); } if (query.id && query.kind) { return this._getById(query.kind, query.id, query); } else if (query.kind) { return this._getByKind(query.kind, query); } const features = new Set(); this._listFeatures(features, new Set(), query); const queryId = query.id; if (queryId) { const filteredFeatures = Array.from(features).filter((f) => f.identifiers.has(queryId)); return new Set(filteredFeatures); } return features; } _getByKind(kind, query = {}) { if (this._featuresByKind && this._isCachable(query)) { // We have a fast index! Use that. const features = this._featuresByKind.get(kind) || new Set(); if (!query.externalPackages) { return this._filterOutExternal(features); } return features; } else if (this._doneResolving && this._isCachable(query)) { // We're done discovering features in this document and its children so // we can safely build up the indexes. this._buildIndexes(); return this._getByKind(kind, query); } return this._getSlowlyByKind(kind, query); } _getById(kind, identifier, query = {}) { if (this._featuresByKindAndId && this._isCachable(query)) { // We have a fast index! Use that. const idMap = this._featuresByKindAndId.get(kind); const features = (idMap && idMap.get(identifier)) || new Set(); if (!query.externalPackages) { return this._filterOutExternal(features); } return features; } else if (this._doneResolving && this._isCachable(query)) { // We're done discovering features in this document and its children so // we can safely build up the indexes. this._buildIndexes(); return this._getById(kind, identifier, query); } const result = new Set(); for (const featureOfKind of this._getByKind(kind, query)) { if (featureOfKind.identifiers.has(identifier)) { result.add(featureOfKind); } } return result; } _getByStatement(statement, kind) { const result = this._localFeaturesByStatement.get(statement); if (kind === undefined) { return result; } return this._filterByKind(result, kind); } /** Filters out the given features by the given kind. */ _filterByKind(features, kind) { const result = new Set(); for (const feature of features) { if (feature.kinds.has(kind)) { result.add(feature); } } return result; } _isCachable(query = {}) { return !!query.imported && !query.noLazyImports && !query.excludeBackreferences; } _getSlowlyByKind(kind, query) { const allFeatures = new Set(); this._listFeatures(allFeatures, new Set(), query); const result = new Set(); for (const feature of allFeatures) { if (feature.kinds.has(kind)) { result.add(feature); } } return result; } /** * Walks the graph of documents, starting from `this`, finding features which * match the given query and adding them to the `result` set. Uses `visited` * to deal with cycles. * * This method is O(numFeatures), though it does not walk documents that are * not relevant to the query (e.g. based on whether the query follows imports, * or excludes lazy imports) */ _listFeatures(result, visited, query) { if (visited.has(this)) { return; } visited.add(this); for (const feature of this._localFeatures) { // Don't include a DocumentBackreference feature in the result set if the // query excludes them. if (!feature.kinds.has('document-backreference') || !query.excludeBackreferences) { result.add(feature); } if (feature.kinds.has('document-backreference') && !query.excludeBackreferences) { const containerDocument = feature.document; result.add(containerDocument); containerDocument._listFeatures(result, visited, query); } if (feature.kinds.has('document')) { feature._listFeatures(result, visited, query); } if (feature.kinds.has('import') && query.imported) { const imprt = feature; const isPackageInternal = imprt.document && !analysis_1.Analysis.isExternal(imprt.url); const externalityOk = query.externalPackages || isPackageInternal; const lazinessOk = !query.noLazyImports || !imprt.lazy; if (imprt.document !== undefined && externalityOk && lazinessOk) { imprt.document._listFeatures(result, visited, query); } } } } _filterOutExternal(features) { const result = new Set(); for (const feature of features) { if (feature.sourceRange && analysis_1.Analysis.isExternal(feature.sourceRange.file)) { continue; } result.add(feature); } return result; } /** * Get warnings for the document and all matched features. */ getWarnings(query = {}) { const warnings = new Set(this.warnings); for (const feature of this.getFeatures(query)) { for (const warning of feature.warnings) { warnings.add(warning); } } return Array.from(warnings); } toString() { return this._toString(new Set()).join('\n'); } _toString(documentsWalked) { let result = [`<Document type=${this.parsedDocument.type} url=${this.url}>\n`]; if (documentsWalked.has(this)) { return result; } documentsWalked.add(this); for (const localFeature of this._localFeatures) { if (localFeature instanceof Document) { result = result.concat(localFeature._toString(documentsWalked).map((line) => ` ${line}`)); } else { let subResult = localFeature.toString(); if (subResult === '[object Object]') { subResult = `<${localFeature.constructor.name} kinds="${Array.from(localFeature.kinds).join(', ')}" ids="${Array.from(localFeature.identifiers).join(',')}">}`; } result.push(` ${subResult}`); } } return result; } stringify() { const inlineDocuments = Array.from(this._localFeatures) .filter((f) => f instanceof Document && f.isInline) .map((d) => d.parsedDocument); return this.parsedDocument.stringify({ inlineDocuments: inlineDocuments }); } _indexFeature(feature) { if (!this._featuresByKind || !this._featuresByKindAndId) { return; } for (const kind of feature.kinds) { const kindSet = this._featuresByKind.get(kind) || new Set(); kindSet.add(feature); this._featuresByKind.set(kind, kindSet); for (const id of feature.identifiers) { const identifiersMap = this._featuresByKindAndId.get(kind) || new Map(); this._featuresByKindAndId.set(kind, identifiersMap); const idSet = identifiersMap.get(id) || new Set(); identifiersMap.set(id, idSet); idSet.add(feature); } } } _buildIndexes() { if (this._featuresByKind) { throw new Error('Tried to build indexes multiple times. This should never happen.'); } if (!this._doneResolving) { throw new Error(`Tried to build indexes before finished resolving. ` + `Need to wait until afterwards or the indexes would be incomplete.`); } this._featuresByKind = new Map(); this._featuresByKindAndId = new Map(); for (const feature of this.getFeatures({ imported: true, externalPackages: true })) { this._indexFeature(feature); } } } exports.Document = Document; //# sourceMappingURL=document.js.map