|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  | } |