component-resource-collector.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. "use strict";
  2. /**
  3. * @license
  4. * Copyright Google LLC All Rights Reserved.
  5. *
  6. * Use of this source code is governed by an MIT-style license that can be
  7. * found in the LICENSE file at https://angular.dev/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. exports.ComponentResourceCollector = void 0;
  11. const path_1 = require("path");
  12. const ts = require("typescript");
  13. const decorators_1 = require("./utils/decorators");
  14. const functions_1 = require("./utils/functions");
  15. const line_mappings_1 = require("./utils/line-mappings");
  16. const property_name_1 = require("./utils/property-name");
  17. /**
  18. * Collector that can be used to find Angular templates and stylesheets referenced within
  19. * given TypeScript source files (inline or external referenced files)
  20. */
  21. class ComponentResourceCollector {
  22. constructor(typeChecker, _fileSystem) {
  23. this.typeChecker = typeChecker;
  24. this._fileSystem = _fileSystem;
  25. this.resolvedTemplates = [];
  26. this.resolvedStylesheets = [];
  27. }
  28. visitNode(node) {
  29. if (node.kind === ts.SyntaxKind.ClassDeclaration) {
  30. this._visitClassDeclaration(node);
  31. }
  32. }
  33. _visitClassDeclaration(node) {
  34. const decorators = ts.getDecorators(node);
  35. if (!decorators || !decorators.length) {
  36. return;
  37. }
  38. const ngDecorators = (0, decorators_1.getAngularDecorators)(this.typeChecker, decorators);
  39. const componentDecorator = ngDecorators.find(dec => dec.name === 'Component');
  40. // In case no "@Component" decorator could be found on the current class, skip.
  41. if (!componentDecorator) {
  42. return;
  43. }
  44. const decoratorCall = componentDecorator.node.expression;
  45. // In case the component decorator call is not valid, skip this class declaration.
  46. if (decoratorCall.arguments.length !== 1) {
  47. return;
  48. }
  49. const componentMetadata = (0, functions_1.unwrapExpression)(decoratorCall.arguments[0]);
  50. // Ensure that the component metadata is an object literal expression.
  51. if (!ts.isObjectLiteralExpression(componentMetadata)) {
  52. return;
  53. }
  54. const sourceFile = node.getSourceFile();
  55. const filePath = this._fileSystem.resolve(sourceFile.fileName);
  56. const sourceFileDirPath = (0, path_1.dirname)(sourceFile.fileName);
  57. // Walk through all component metadata properties and determine the referenced
  58. // HTML templates (either external or inline)
  59. componentMetadata.properties.forEach(property => {
  60. if (!ts.isPropertyAssignment(property)) {
  61. return;
  62. }
  63. const propertyName = (0, property_name_1.getPropertyNameText)(property.name);
  64. if (propertyName === 'styles') {
  65. const elements = ts.isArrayLiteralExpression(property.initializer)
  66. ? property.initializer.elements
  67. : [property.initializer];
  68. elements.forEach(el => {
  69. if (ts.isStringLiteralLike(el)) {
  70. // Need to add an offset of one to the start because the template quotes are
  71. // not part of the template content.
  72. const templateStartIdx = el.getStart() + 1;
  73. const content = stripBom(el.text);
  74. this.resolvedStylesheets.push({
  75. filePath,
  76. container: node,
  77. content,
  78. inline: true,
  79. start: templateStartIdx,
  80. getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx),
  81. });
  82. }
  83. });
  84. }
  85. // In case there is an inline template specified, ensure that the value is statically
  86. // analyzable by checking if the initializer is a string literal-like node.
  87. if (propertyName === 'template' && ts.isStringLiteralLike(property.initializer)) {
  88. // Need to add an offset of one to the start because the template quotes are
  89. // not part of the template content.
  90. const templateStartIdx = property.initializer.getStart() + 1;
  91. this.resolvedTemplates.push({
  92. filePath,
  93. container: node,
  94. content: property.initializer.text,
  95. inline: true,
  96. start: templateStartIdx,
  97. getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx),
  98. });
  99. }
  100. if (propertyName === 'styleUrls' && ts.isArrayLiteralExpression(property.initializer)) {
  101. property.initializer.elements.forEach(el => {
  102. if (ts.isStringLiteralLike(el)) {
  103. this._trackExternalStylesheet(sourceFileDirPath, el, node);
  104. }
  105. });
  106. }
  107. if (propertyName === 'styleUrl' && ts.isStringLiteralLike(property.initializer)) {
  108. this._trackExternalStylesheet(sourceFileDirPath, property.initializer, node);
  109. }
  110. if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) {
  111. const templateUrl = property.initializer.text;
  112. const templatePath = this._fileSystem.resolve(sourceFileDirPath, templateUrl);
  113. // In case the template does not exist in the file system, skip this
  114. // external template.
  115. if (!this._fileSystem.fileExists(templatePath)) {
  116. return;
  117. }
  118. const fileContent = stripBom(this._fileSystem.read(templatePath) || '');
  119. if (fileContent) {
  120. const lineStartsMap = (0, line_mappings_1.computeLineStartsMap)(fileContent);
  121. this.resolvedTemplates.push({
  122. filePath: templatePath,
  123. container: node,
  124. content: fileContent,
  125. inline: false,
  126. start: 0,
  127. getCharacterAndLineOfPosition: p => (0, line_mappings_1.getLineAndCharacterFromPosition)(lineStartsMap, p),
  128. });
  129. }
  130. }
  131. });
  132. }
  133. /** Resolves an external stylesheet by reading its content and computing line mappings. */
  134. resolveExternalStylesheet(filePath, container) {
  135. // Strip the BOM to avoid issues with the Sass compiler. See:
  136. // https://github.com/angular/components/issues/24227#issuecomment-1200934258
  137. const fileContent = stripBom(this._fileSystem.read(filePath) || '');
  138. if (!fileContent) {
  139. return null;
  140. }
  141. const lineStartsMap = (0, line_mappings_1.computeLineStartsMap)(fileContent);
  142. return {
  143. filePath: filePath,
  144. container: container,
  145. content: fileContent,
  146. inline: false,
  147. start: 0,
  148. getCharacterAndLineOfPosition: pos => (0, line_mappings_1.getLineAndCharacterFromPosition)(lineStartsMap, pos),
  149. };
  150. }
  151. _trackExternalStylesheet(sourceFileDirPath, node, container) {
  152. const stylesheetPath = this._fileSystem.resolve(sourceFileDirPath, node.text);
  153. const stylesheet = this.resolveExternalStylesheet(stylesheetPath, container);
  154. if (stylesheet) {
  155. this.resolvedStylesheets.push(stylesheet);
  156. }
  157. }
  158. }
  159. exports.ComponentResourceCollector = ComponentResourceCollector;
  160. /** Strips the BOM from a string. */
  161. function stripBom(content) {
  162. return content.replace(/\uFEFF/g, '');
  163. }
  164. //# sourceMappingURL=component-resource-collector.js.map