component-resource-collector.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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.io/license
  8. */
  9. Object.defineProperty(exports, "__esModule", { value: true });
  10. const fs_1 = require("fs");
  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) {
  23. this.typeChecker = typeChecker;
  24. this.resolvedTemplates = [];
  25. this.resolvedStylesheets = [];
  26. }
  27. visitNode(node) {
  28. if (node.kind === ts.SyntaxKind.ClassDeclaration) {
  29. this._visitClassDeclaration(node);
  30. }
  31. }
  32. _visitClassDeclaration(node) {
  33. if (!node.decorators || !node.decorators.length) {
  34. return;
  35. }
  36. const ngDecorators = decorators_1.getAngularDecorators(this.typeChecker, node.decorators);
  37. const componentDecorator = ngDecorators.find(dec => dec.name === 'Component');
  38. // In case no "@Component" decorator could be found on the current class, skip.
  39. if (!componentDecorator) {
  40. return;
  41. }
  42. const decoratorCall = componentDecorator.node.expression;
  43. // In case the component decorator call is not valid, skip this class declaration.
  44. if (decoratorCall.arguments.length !== 1) {
  45. return;
  46. }
  47. const componentMetadata = functions_1.unwrapExpression(decoratorCall.arguments[0]);
  48. // Ensure that the component metadata is an object literal expression.
  49. if (!ts.isObjectLiteralExpression(componentMetadata)) {
  50. return;
  51. }
  52. const sourceFile = node.getSourceFile();
  53. const sourceFileName = sourceFile.fileName;
  54. // Walk through all component metadata properties and determine the referenced
  55. // HTML templates (either external or inline)
  56. componentMetadata.properties.forEach(property => {
  57. if (!ts.isPropertyAssignment(property)) {
  58. return;
  59. }
  60. const propertyName = property_name_1.getPropertyNameText(property.name);
  61. const filePath = path_1.resolve(sourceFileName);
  62. if (propertyName === 'styles' && ts.isArrayLiteralExpression(property.initializer)) {
  63. property.initializer.elements.forEach(el => {
  64. if (ts.isStringLiteralLike(el)) {
  65. // Need to add an offset of one to the start because the template quotes are
  66. // not part of the template content.
  67. const templateStartIdx = el.getStart() + 1;
  68. this.resolvedStylesheets.push({
  69. filePath: filePath,
  70. container: node,
  71. content: el.text,
  72. inline: true,
  73. start: templateStartIdx,
  74. getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx),
  75. });
  76. }
  77. });
  78. }
  79. // In case there is an inline template specified, ensure that the value is statically
  80. // analyzable by checking if the initializer is a string literal-like node.
  81. if (propertyName === 'template' && ts.isStringLiteralLike(property.initializer)) {
  82. // Need to add an offset of one to the start because the template quotes are
  83. // not part of the template content.
  84. const templateStartIdx = property.initializer.getStart() + 1;
  85. this.resolvedTemplates.push({
  86. filePath: filePath,
  87. container: node,
  88. content: property.initializer.text,
  89. inline: true,
  90. start: templateStartIdx,
  91. getCharacterAndLineOfPosition: pos => ts.getLineAndCharacterOfPosition(sourceFile, pos + templateStartIdx)
  92. });
  93. }
  94. if (propertyName === 'styleUrls' && ts.isArrayLiteralExpression(property.initializer)) {
  95. property.initializer.elements.forEach(el => {
  96. if (ts.isStringLiteralLike(el)) {
  97. const stylesheetPath = path_1.resolve(path_1.dirname(sourceFileName), el.text);
  98. // In case the stylesheet does not exist in the file system, skip it gracefully.
  99. if (!fs_1.existsSync(stylesheetPath)) {
  100. return;
  101. }
  102. this.resolvedStylesheets.push(this.resolveExternalStylesheet(stylesheetPath, node));
  103. }
  104. });
  105. }
  106. if (propertyName === 'templateUrl' && ts.isStringLiteralLike(property.initializer)) {
  107. const templatePath = path_1.resolve(path_1.dirname(sourceFileName), property.initializer.text);
  108. // In case the template does not exist in the file system, skip this
  109. // external template.
  110. if (!fs_1.existsSync(templatePath)) {
  111. return;
  112. }
  113. const fileContent = fs_1.readFileSync(templatePath, 'utf8');
  114. const lineStartsMap = line_mappings_1.computeLineStartsMap(fileContent);
  115. this.resolvedTemplates.push({
  116. filePath: templatePath,
  117. container: node,
  118. content: fileContent,
  119. inline: false,
  120. start: 0,
  121. getCharacterAndLineOfPosition: pos => line_mappings_1.getLineAndCharacterFromPosition(lineStartsMap, pos),
  122. });
  123. }
  124. });
  125. }
  126. /** Resolves an external stylesheet by reading its content and computing line mappings. */
  127. resolveExternalStylesheet(filePath, container) {
  128. const fileContent = fs_1.readFileSync(filePath, 'utf8');
  129. const lineStartsMap = line_mappings_1.computeLineStartsMap(fileContent);
  130. return {
  131. filePath: filePath,
  132. container: container,
  133. content: fileContent,
  134. inline: false,
  135. start: 0,
  136. getCharacterAndLineOfPosition: pos => line_mappings_1.getLineAndCharacterFromPosition(lineStartsMap, pos),
  137. };
  138. }
  139. }
  140. exports.ComponentResourceCollector = ComponentResourceCollector;
  141. //# sourceMappingURL=component-resource-collector.js.map