UNPKG

@angular/cdk

Version:

Angular Material Component Development Kit

164 lines 7.92 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ComponentResourceCollector = void 0; const path_1 = require("path"); const ts = require("typescript"); const decorators_1 = require("./utils/decorators"); const functions_1 = require("./utils/functions"); const line_mappings_1 = require("./utils/line-mappings"); const property_name_1 = require("./utils/property-name"); /** * Collector that can be used to find Angular templates and stylesheets referenced within * given TypeScript source files (inline or external referenced files) */ class ComponentResourceCollector { constructor(typeChecker, _fileSystem) { this.typeChecker = typeChecker; this._fileSystem = _fileSystem; this.resolvedTemplates = []; this.resolvedStylesheets = []; } visitNode(node) { if (node.kind === ts.SyntaxKind.ClassDeclaration) { this._visitClassDeclaration(node); } } _visitClassDeclaration(node) { const decorators = ts.getDecorators(node); if (!decorators || !decorators.length) { return; } const ngDecorators = (0, decorators_1.getAngularDecorators)(this.typeChecker, decorators); const componentDecorator = ngDecorators.find(dec => dec.name === 'Component'); // In case no "@Component" decorator could be found on the current class, skip. if (!componentDecorator) { return; } const decoratorCall = componentDecorator.node.expression; // In case the component decorator call is not valid, skip this class declaration. if (decoratorCall.arguments.length !== 1) { return; } const componentMetadata = (0, functions_1.unwrapExpression)(decoratorCall.arguments[0]); // Ensure that the component metadata is an object literal expression. if (!ts.isObjectLiteralExpression(componentMetadata)) { return; } const sourceFile = node.getSourceFile(); const filePath = this._fileSystem.resolve(sourceFile.fileName); const sourceFileDirPath = (0, path_1.dirname)(sourceFile.fileName); // Walk through all component metadata properties and determine the referenced // HTML templates (either external or inline) componentMetadata.properties.forEach(property => { if (!ts.isPropertyAssignment(property)) { return; } const propertyName = (0, property_name_1.getPropertyNameText)(property.name); if (propertyName === 'styles') { const elements = ts.isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer]; elements.forEach(el => { if (ts.isStringLiteralLike(el)) { // Need to add an offset of one to the start because the template quotes are // not part of the template content. const templateStartIdx = el.getStart() + 1; const content = stripBom(el.text); this.resolvedStylesheets.push({ filePath, container: node, content, inline: true, start: templateStartIdx, getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx), }); } }); } // In case there is an inline template specified, ensure that the value is statically // analyzable by checking if the initializer is a string literal-like node. if (propertyName === 'template' && ts.isStringLiteralLike(property.initializer)) { // Need to add an offset of one to the start because the template quotes are // not part of the template content. const templateStartIdx = property.initializer.getStart() + 1; this.resolvedTemplates.push({ filePath, container: node, content: property.initializer.text, inline: true, start: templateStartIdx, getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx), }); } if (propertyName === 'styleUrls' && ts.isArrayLiteralExpression(property.initializer)) { property.initializer.elements.forEach(el => { if (ts.isStringLiteralLike(el)) { this._trackExternalStylesheet(sourceFileDirPath, el, node); } }); } if (propertyName === 'styleUrl' && ts.isStringLiteralLike(property.initializer)) { this._trackExternalStylesheet(sourceFileDirPath, property.initializer, node); } if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) { const templateUrl = property.initializer.text; const templatePath = this._fileSystem.resolve(sourceFileDirPath, templateUrl); // In case the template does not exist in the file system, skip this // external template. if (!this._fileSystem.fileExists(templatePath)) { return; } const fileContent = stripBom(this._fileSystem.read(templatePath) || ''); if (fileContent) { const lineStartsMap = (0, line_mappings_1.computeLineStartsMap)(fileContent); this.resolvedTemplates.push({ filePath: templatePath, container: node, content: fileContent, inline: false, start: 0, getCharacterAndLineOfPosition: p => (0, line_mappings_1.getLineAndCharacterFromPosition)(lineStartsMap, p), }); } } }); } /** Resolves an external stylesheet by reading its content and computing line mappings. */ resolveExternalStylesheet(filePath, container) { // Strip the BOM to avoid issues with the Sass compiler. See: // https://github.com/angular/components/issues/24227#issuecomment-1200934258 const fileContent = stripBom(this._fileSystem.read(filePath) || ''); if (!fileContent) { return null; } const lineStartsMap = (0, line_mappings_1.computeLineStartsMap)(fileContent); return { filePath: filePath, container: container, content: fileContent, inline: false, start: 0, getCharacterAndLineOfPosition: pos => (0, line_mappings_1.getLineAndCharacterFromPosition)(lineStartsMap, pos), }; } _trackExternalStylesheet(sourceFileDirPath, node, container) { const stylesheetPath = this._fileSystem.resolve(sourceFileDirPath, node.text); const stylesheet = this.resolveExternalStylesheet(stylesheetPath, container); if (stylesheet) { this.resolvedStylesheets.push(stylesheet); } } } exports.ComponentResourceCollector = ComponentResourceCollector; /** Strips the BOM from a string. */ function stripBom(content) { return content.replace(/\uFEFF/g, ''); } //# sourceMappingURL=component-resource-collector.js.map