164 lines
7.7 KiB
JavaScript
Executable File
164 lines
7.7 KiB
JavaScript
Executable File
"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
|