noUnusedCssRule.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. "use strict";
  2. var __extends = (this && this.__extends) || (function () {
  3. var extendStatics = function (d, b) {
  4. extendStatics = Object.setPrototypeOf ||
  5. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  6. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  7. return extendStatics(d, b);
  8. };
  9. return function (d, b) {
  10. extendStatics(d, b);
  11. function __() { this.constructor = d; }
  12. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  13. };
  14. })();
  15. Object.defineProperty(exports, "__esModule", { value: true });
  16. var compiler_1 = require("@angular/compiler");
  17. var Lint = require("tslint");
  18. var ngWalker_1 = require("./angular/ngWalker");
  19. var basicCssAstVisitor_1 = require("./angular/styles/basicCssAstVisitor");
  20. var basicTemplateAstVisitor_1 = require("./angular/templates/basicTemplateAstVisitor");
  21. var templateParser_1 = require("./angular/templates/templateParser");
  22. var logger_1 = require("./util/logger");
  23. var ngVersion_1 = require("./util/ngVersion");
  24. var utils_1 = require("./util/utils");
  25. var CssSelectorTokenizer = require('css-selector-tokenizer');
  26. var isEncapsulationEnabled = function (encapsulation) {
  27. if (!encapsulation) {
  28. return true;
  29. }
  30. if (utils_1.getSymbolName(encapsulation) !== 'ViewEncapsulation') {
  31. return false;
  32. }
  33. var encapsulationType = encapsulation.name.text;
  34. return /^(Emulated|Native)$/.test(encapsulationType);
  35. };
  36. var lang = require('cssauron')({
  37. tag: function (node) {
  38. return (node.name || '').toLowerCase();
  39. },
  40. contents: function (node) {
  41. return '';
  42. },
  43. id: function (node) {
  44. return this.attr(node, 'id');
  45. },
  46. class: function (node) {
  47. var classBindings = (node.inputs || [])
  48. .filter(function (b) { return b.type === 2; })
  49. .map(function (b) { return b.name; })
  50. .join(' ');
  51. var classAttr = node.attrs.find(function (a) { return a.name.toLowerCase() === 'class'; });
  52. return classAttr ? classAttr.value + " " + classBindings : classBindings;
  53. },
  54. parent: function (node) {
  55. return node.parentNode;
  56. },
  57. children: function (node) {
  58. return node.children;
  59. },
  60. attr: function (node, attr) {
  61. var targetAttr = node.attrs.find(function (a) { return a.name === attr; });
  62. return targetAttr ? targetAttr.value : undefined;
  63. }
  64. });
  65. var ElementVisitor = (function (_super) {
  66. __extends(ElementVisitor, _super);
  67. function ElementVisitor() {
  68. return _super !== null && _super.apply(this, arguments) || this;
  69. }
  70. ElementVisitor.prototype.visitElement = function (ast, fn) {
  71. var _this = this;
  72. fn(ast);
  73. ast.children.forEach(function (c) {
  74. if (c instanceof compiler_1.ElementAst) {
  75. c.parentNode = ast;
  76. }
  77. _this.visit(c, fn);
  78. });
  79. };
  80. return ElementVisitor;
  81. }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
  82. var hasSelector = function (s, type) {
  83. if (!s) {
  84. return false;
  85. }
  86. return s.type === 'selector' || s.type === 'selectors' ? (s.nodes || []).some(function (n) { return hasSelector(n, type); }) : s.type === type;
  87. };
  88. var dynamicFilters = {
  89. id: function (ast) {
  90. return (ast.inputs || []).some(function (i) { return i.name === 'id'; });
  91. },
  92. attribute: function (ast) {
  93. return (ast.inputs || []).some(function (i) { return i.type === 1; });
  94. },
  95. class: function (ast) {
  96. return (ast.inputs || []).some(function (i) { return i.name === 'className' || i.name === 'ngClass'; });
  97. }
  98. };
  99. var ElementFilterVisitor = (function (_super) {
  100. __extends(ElementFilterVisitor, _super);
  101. function ElementFilterVisitor() {
  102. return _super !== null && _super.apply(this, arguments) || this;
  103. }
  104. ElementFilterVisitor.prototype.shouldVisit = function (ast, strategies, selectorTypes) {
  105. var _this = this;
  106. return (Object.keys(strategies).every(function (s) {
  107. var strategy = strategies[s];
  108. return !selectorTypes[s] || !strategy(ast);
  109. }) &&
  110. (ast.children || []).every(function (c) {
  111. return (ast instanceof compiler_1.ElementAst && _this.shouldVisit(c, strategies, selectorTypes)) ||
  112. (ast instanceof compiler_1.EmbeddedTemplateAst &&
  113. (ast.children || []).every(function (c) { return _this.shouldVisit(c, strategies, selectorTypes); }));
  114. }));
  115. };
  116. return ElementFilterVisitor;
  117. }(basicTemplateAstVisitor_1.BasicTemplateAstVisitor));
  118. var Rule = (function (_super) {
  119. __extends(Rule, _super);
  120. function Rule() {
  121. return _super !== null && _super.apply(this, arguments) || this;
  122. }
  123. Rule.prototype.apply = function (sourceFile) {
  124. var walkerConfig = { cssVisitorCtrl: CssVisitorCtrl };
  125. var walker = new Walker(sourceFile, this.getOptions(), walkerConfig);
  126. return this.applyWithWalker(walker);
  127. };
  128. Rule.metadata = {
  129. ruleName: 'no-unused-css',
  130. type: 'maintainability',
  131. description: "Disallows having an unused CSS rule in the component's stylesheet.",
  132. options: null,
  133. optionsDescription: 'Not configurable.',
  134. typescriptOnly: true,
  135. hasFix: true
  136. };
  137. return Rule;
  138. }(Lint.Rules.AbstractRule));
  139. exports.Rule = Rule;
  140. var CssVisitorCtrl = (function (_super) {
  141. __extends(CssVisitorCtrl, _super);
  142. function CssVisitorCtrl(sourceFile, originalOptions, context, style, templateStart) {
  143. var _this = _super.call(this, sourceFile, originalOptions, context, style, templateStart) || this;
  144. _this.style = style;
  145. return _this;
  146. }
  147. CssVisitorCtrl.prototype.visitCssSelectorRule = function (ast) {
  148. var _this = this;
  149. try {
  150. var match = ast.selectors.some(function (s) { return _this.visitCssSelector(s); });
  151. if (!match) {
  152. var endOffset = ast.end.offset, startOffset = ast.start.offset;
  153. var length_1 = endOffset - startOffset + 1;
  154. this.addFailureAt(startOffset, length_1, 'Unused styles', Lint.Replacement.deleteText(startOffset - 1, length_1 + 1));
  155. }
  156. }
  157. catch (e) {
  158. logger_1.logger.error(e);
  159. }
  160. return true;
  161. };
  162. CssVisitorCtrl.prototype.visitCssSelector = function (ast) {
  163. var parts = [];
  164. for (var i = 0; i < ast.selectorParts.length; i += 1) {
  165. var c = ast.selectorParts[i];
  166. c.strValue = c.strValue.split('::').shift();
  167. if (c.strValue.endsWith('/') || c.strValue.endsWith('>')) {
  168. parts.push(c.strValue);
  169. break;
  170. }
  171. else if (!c.strValue.startsWith(':')) {
  172. parts.push(c.strValue);
  173. }
  174. }
  175. if (!parts.length || !this.templateAst) {
  176. return true;
  177. }
  178. var strippedSelector = parts.map(function (s) { return s.replace(/\/|>$/, '').trim(); }).join(' ');
  179. var elementFilterVisitor = new ElementFilterVisitor(this.getSourceFile(), this._originalOptions, this.context, 0);
  180. var tokenized = CssSelectorTokenizer.parse(strippedSelector);
  181. var selectorTypesCache = Object.keys(dynamicFilters).reduce(function (a, key) {
  182. a[key] = hasSelector(tokenized, key);
  183. return a;
  184. }, {});
  185. if (!elementFilterVisitor.shouldVisit(this.templateAst, dynamicFilters, selectorTypesCache)) {
  186. return true;
  187. }
  188. var matchFound = false;
  189. var selector = function (element) {
  190. if (lang(strippedSelector)(element)) {
  191. matchFound = true;
  192. return true;
  193. }
  194. return false;
  195. };
  196. var visitor = new ElementVisitor(this.getSourceFile(), this._originalOptions, this.context, 0);
  197. visitor.visit(this.templateAst, selector);
  198. return matchFound;
  199. };
  200. return CssVisitorCtrl;
  201. }(basicCssAstVisitor_1.BasicCssAstVisitor));
  202. var Walker = (function (_super) {
  203. __extends(Walker, _super);
  204. function Walker() {
  205. return _super !== null && _super.apply(this, arguments) || this;
  206. }
  207. Walker.prototype.visitClassDeclaration = function (declaration) {
  208. var _this = this;
  209. var d = utils_1.getComponentDecorator(declaration);
  210. if (d) {
  211. var meta_1 = this._metadataReader.read(declaration);
  212. this.visitNgComponent(meta_1);
  213. if (meta_1.template && meta_1.template.template) {
  214. try {
  215. var ElementAstCtr_1 = compiler_1.ElementAst;
  216. ngVersion_1.SemVerDSL.gte('4.0.0-beta.8', function () {
  217. _this.templateAst = new ElementAstCtr_1('*', [], [], [], [], [], [], false, [], templateParser_1.parseTemplate(meta_1.template.template.code), 0, null, null);
  218. }).else(function () {
  219. _this.templateAst = new ElementAstCtr_1('*', [], [], [], [], [], [], false, templateParser_1.parseTemplate(meta_1.template.template.code), 0, null, null);
  220. });
  221. }
  222. catch (e) {
  223. logger_1.logger.error('Cannot parse the template', e);
  224. }
  225. }
  226. }
  227. _super.prototype.visitClassDeclaration.call(this, declaration);
  228. };
  229. Walker.prototype.visitNgStyleHelper = function (style, context, styleMetadata, baseStart) {
  230. this.validateStyles(style, context, styleMetadata, baseStart);
  231. _super.prototype.visitNgStyleHelper.call(this, style, context, styleMetadata, baseStart);
  232. };
  233. Walker.prototype.validateStyles = function (style, context, styleMetadata, baseStart) {
  234. var _this = this;
  235. if (!style) {
  236. return;
  237. }
  238. var file = this.getContextSourceFile(styleMetadata.url, styleMetadata.style.source);
  239. var visitor = new CssVisitorCtrl(file, this._originalOptions, context, styleMetadata, baseStart);
  240. visitor.templateAst = this.templateAst;
  241. var d = utils_1.getComponentDecorator(context.controller);
  242. var encapsulation = utils_1.getDecoratorPropertyInitializer(d, 'encapsulation');
  243. if (isEncapsulationEnabled(encapsulation)) {
  244. style.visit(visitor);
  245. visitor.getFailures().forEach(function (f) { return _this.addFailure(f); });
  246. }
  247. };
  248. return Walker;
  249. }(ngWalker_1.NgWalker));