UNPKG

3.3 kBPlain TextView Raw
1#!/usr/bin/env node
2
3const AssetGraph = require('../lib/AssetGraph');
4const _ = require('lodash');
5const urlTools = require('urltools');
6const commandLineOptions = require('yargs')
7 .usage('$0 [--root <inputRootDirectory>] <htmlFile>...')
8 .demand(1).argv;
9
10const ignorePseudoClasses = [
11 'active',
12 'checked',
13 'default',
14 'disabled',
15 'empty',
16 'enabled',
17 'first',
18 'fullscreen',
19 'focus',
20 'hover',
21 'indeterminate',
22 'in-range',
23 'invalid',
24 'visited',
25 'left',
26 'right',
27 'read-only',
28 'read-write',
29 'optional',
30 'out-of-range',
31 'link',
32];
33
34// Pseudo-classes intentionally not on the above list because they make sense
35// on a non-live document HTML:
36// first-child, first-of-type, last-child, last-of-type, only-child, only-of-type, required, root, scope, target, valid
37
38// Give up:
39// lang() not() nth-child() nth-last-child() nth-last-of-type() nth-of-type()
40
41new AssetGraph({ root: commandLineOptions.root })
42 .logEvents({
43 repl: commandLineOptions.repl,
44 stopOnWarning: commandLineOptions.stoponwarning,
45 suppressJavaScriptCommonJsRequireWarnings: true,
46 })
47 .loadAssets(commandLineOptions._.map(urlTools.fsFilePathToFileUrl))
48 .populate({
49 followRelations: {
50 type: ['HtmlStyle', 'CssImport'],
51 to: { url: /^file:/ },
52 },
53 })
54 .queue(function (assetGraph) {
55 var documents = _.map(assetGraph.findAssets({ type: 'Html' }), 'parseTree');
56 function isSelectorUsed(selector) {
57 var giveUp = false;
58 // Preprocess pseudo-classes
59 selector = selector.replace(
60 /:([a-z-]+)(\()?/g,
61 function ($0, pseudoClassName, leadingParen) {
62 if (leadingParen) {
63 // Give up on :not(...) etc., anything that takes parameters
64 giveUp = true;
65 return $0;
66 } else if (ignorePseudoClasses.indexOf(pseudoClassName) !== -1) {
67 return '';
68 } else {
69 return $0;
70 }
71 }
72 );
73 if (giveUp) {
74 return true;
75 }
76
77 return documents.some(function (document) {
78 try {
79 return document.querySelectorAll(selector).length > 0;
80 } catch (e) {
81 // Assume that selectors jsdom doesn't support are in use:
82 return true;
83 }
84 });
85 }
86 assetGraph.findAssets({ type: 'Css' }).forEach(function (cssAsset) {
87 cssAsset.constructor.eachRuleInParseTree(
88 cssAsset.parseTree,
89 function (cssRule) {
90 if (cssRule.type === 'rule') {
91 // STYLE_RULE
92 var selectors = cssRule.selectors;
93 var unusedSelectors = cssRule.selectors.filter(function (selector) {
94 return !isSelectorUsed(selector);
95 });
96 if (unusedSelectors.length > 0) {
97 if (unusedSelectors.length === selectors.length) {
98 console.warn('Unused rule: ' + cssRule.toString());
99 } else {
100 console.warn(
101 'Unused selector' +
102 (unusedSelectors.length === 1 ? '' : 's') +
103 ' "' +
104 unusedSelectors.join('", "') +
105 '" in rule: ' +
106 cssRule.toString()
107 );
108 }
109 }
110 }
111 }
112 );
113 });
114 })
115 .run();