| const fs = require('fs'); |
| const path = require('path'); |
| const css = require('css'); |
| const cssWhat = require('css-what'); |
| const acorn = require('acorn'); |
| |
| const utils = require('../utils'); |
| const promisify = require('util').promisify; |
| const readFile = promisify(fs.readFile); |
| const FRONTEND_PATH = path.join(__dirname, '..', '..', 'front_end'); |
| |
| const classes = new Set(); |
| const strings = new Set(); |
| const trickyStrings = new Set([ |
| 'crc-node__tree-hostname', |
| 'tooltip-boundary', |
| 'terminal', |
| 'terminal-cursor', |
| 'composition-view' |
| ]); |
| (async function() { |
| await Promise.all(fs.readdirSync(FRONTEND_PATH).map(dir => processFolder(dir))); |
| const unused = []; |
| for (const className of classes) { |
| if (strings.has(className) || trickyStrings.has(className)) |
| continue; |
| if (className.startsWith('CodeMirror')) |
| continue; |
| if (className.startsWith('xterm-')) |
| continue; |
| if (className.startsWith('lh-')) |
| continue; |
| if (className.startsWith('cm-')) |
| continue; |
| if (className.startsWith('navigator-')) |
| continue; |
| if (className.startsWith('object-value-')) |
| continue; |
| if (className.startsWith('security-summary-')) |
| continue; |
| if (className.startsWith('security-explanation-title-')) |
| continue; |
| if (className.startsWith('security-explanation-')) |
| continue; |
| if (className.startsWith('lock-icon-')) |
| continue; |
| if (className.startsWith('security-property-')) |
| continue; |
| if (className.startsWith('url-scheme-')) |
| continue; |
| if (className.startsWith('infobar-')) |
| continue; |
| if (className.startsWith('shadow-root-depth-')) |
| continue; |
| if (className.startsWith('timeline-overview-')) |
| continue; |
| if (className.startsWith('spritesheet-')) |
| continue; |
| if (className.startsWith('report-icon--')) |
| continue; |
| |
| if (checkSuffix('-start')) |
| continue; |
| if (checkSuffix('-end')) |
| continue; |
| if (checkSuffix('-column')) |
| continue; |
| if (checkSuffix('-overview-grid')) |
| continue; |
| if (checkSuffix('-overview-container')) |
| continue; |
| if (checkSuffix('-icon')) |
| continue; |
| unused.push(className); |
| |
| function checkSuffix(suffix) { |
| return className.endsWith(suffix) && strings.has(className.substring(0, className.length - suffix.length)); |
| } |
| } |
| console.log(unused); |
| console.log(unused.length); |
| })(); |
| |
| |
| async function processFolder(dir) { |
| if (!utils.isDir(path.join(FRONTEND_PATH, dir))) |
| return; |
| const modulePath = path.join(FRONTEND_PATH, dir, 'module.json'); |
| if (!utils.isFile(modulePath)) |
| return; |
| const content = JSON.parse(await readFile(modulePath, 'utf8')); |
| const promises = []; |
| for (const resource of content.resources || []) { |
| if (!resource.endsWith('.css')) |
| continue; |
| promises.push(processCSSFile(path.join(FRONTEND_PATH, dir, resource))); |
| } |
| const skips = new Set(content.skip_compilation || []); |
| for (const script of content.scripts || []) { |
| if (skips.has(script)) |
| continue; |
| promises.push(processScriptFile(path.join(FRONTEND_PATH, dir, script))); |
| } |
| await Promise.all(promises); |
| } |
| |
| async function processCSSFile(cssFile) { |
| const content = await readFile(cssFile, 'utf8'); |
| try { |
| const ast = css.parse(content); |
| for (const rule of ast.stylesheet.rules) { |
| for (const selector of rule.selectors || []) { |
| for (const token of parseSimpleSelector(selector)) { |
| if (token.name === 'class' || token.name === 'id') |
| classes.add(token.value); |
| } |
| } |
| } |
| } catch(e) { |
| console.log(cssFile, e) |
| } |
| } |
| |
| function parseSimpleSelector(selector) { |
| // css-what isn't the best. Try catch. |
| try { |
| const parsed = cssWhat(selector) |
| return parsed[0] || []; |
| } catch(e) { |
| return []; |
| } |
| } |
| |
| async function processScriptFile(scriptFile) { |
| const content = await readFile(scriptFile, 'utf8'); |
| const tokens = acorn.tokenizer(content); |
| for (const token of tokens) { |
| if(token.type.label === 'string' || token.type.label === 'template') { |
| for (const word of token.value.split(' ')) |
| strings.add(word); |
| const regex = /class\s*=\s*['"]?([\w\-_ ]*)/ig; |
| let result; |
| while ((result = regex.exec(token.value))) { |
| for (const word of result[1].split(' ')) |
| strings.add(word); |
| } |
| } |
| } |
| } |