From 5d623c039d5ca8aaed88ae96e0e03ee4075a281f Mon Sep 17 00:00:00 2001 From: Brendan Kenny Date: Fri, 11 May 2018 15:48:06 -0700 Subject: [PATCH 1/5] core(tsc): add tsc type checking to report --- .../dependency-graph/simulator/simulator.js | 2 +- .../report/html/renderer/category-renderer.js | 102 ++++---- .../html/renderer/crc-details-renderer.js | 89 +++---- .../report/html/renderer/details-renderer.js | 218 +++++++++--------- lighthouse-core/report/html/renderer/dom.js | 45 ++-- .../report/html/renderer/logger.js | 11 +- .../renderer/performance-category-renderer.js | 35 +-- .../report/html/renderer/report-renderer.js | 132 ++++++----- .../html/renderer/report-ui-features.js | 107 +++++---- lighthouse-core/report/html/renderer/util.js | 13 +- package.json | 2 +- tsconfig.json | 7 +- typings/artifacts.d.ts | 4 +- typings/config.d.ts | 5 +- typings/gatherer.d.ts | 10 +- typings/html-renderer.d.ts | 41 ++++ yarn.lock | 6 +- 17 files changed, 438 insertions(+), 391 deletions(-) create mode 100644 typings/html-renderer.d.ts diff --git a/lighthouse-core/lib/dependency-graph/simulator/simulator.js b/lighthouse-core/lib/dependency-graph/simulator/simulator.js index ac103275eeaa..661a86a49905 100644 --- a/lighthouse-core/lib/dependency-graph/simulator/simulator.js +++ b/lighthouse-core/lib/dependency-graph/simulator/simulator.js @@ -317,7 +317,7 @@ class Simulator { simulate(graph, options) { options = Object.assign({flexibleOrdering: false}, options); // initialize the necessary data containers - this._flexibleOrdering = options.flexibleOrdering; + this._flexibleOrdering = !!options.flexibleOrdering; this._initializeConnectionPool(graph); this._initializeAuxiliaryData(); diff --git a/lighthouse-core/report/html/renderer/category-renderer.js b/lighthouse-core/report/html/renderer/category-renderer.js index 0796b7c12460..59d228359681 100644 --- a/lighthouse-core/report/html/renderer/category-renderer.js +++ b/lighthouse-core/report/html/renderer/category-renderer.js @@ -7,26 +7,34 @@ /* globals self, Util */ +/** @typedef {import('./dom.js')} DOM */ +/** @typedef {import('./report-renderer.js')} ReportRenderer */ +/** @typedef {import('./report-renderer.js').AuditJSON} AuditJSON */ +/** @typedef {import('./report-renderer.js').CategoryJSON} CategoryJSON */ +/** @typedef {import('./report-renderer.js').GroupJSON} GroupJSON */ +/** @typedef {import('./details-renderer.js')} DetailsRenderer */ +/** @typedef {import('./util.js')} Util */ + class CategoryRenderer { /** - * @param {!DOM} dom - * @param {!DetailsRenderer} detailsRenderer + * @param {DOM} dom + * @param {DetailsRenderer} detailsRenderer */ constructor(dom, detailsRenderer) { - /** @protected {!DOM} */ + /** @type {DOM} */ this.dom = dom; - /** @protected {!DetailsRenderer} */ + /** @type {DetailsRenderer} */ this.detailsRenderer = detailsRenderer; - /** @protected {!Document|!Element} */ + /** @type {ParentNode} */ this.templateContext = this.dom.document(); this.detailsRenderer.setTemplateContext(this.templateContext); } /** - * @param {!ReportRenderer.AuditJSON} audit + * @param {AuditJSON} audit * @param {number} index - * @return {!Element} + * @return {Element} */ renderAudit(audit, index) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-audit', this.templateContext); @@ -45,7 +53,7 @@ class CategoryRenderer { .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText)); // Append audit details to header section so the entire audit is within a
. - const header = /** @type {!HTMLDetailsElement} */ (this.dom.find('details', auditEl)); + const header = /** @type {HTMLDetailsElement} */ (this.dom.find('details', auditEl)); if (audit.result.details && audit.result.details.type) { const elem = this.detailsRenderer.render(audit.result.details); elem.classList.add('lh-details'); @@ -73,10 +81,10 @@ class CategoryRenderer { } /** - * @param {!Element} element DOM node to populate with values. + * @param {Element} element DOM node to populate with values. * @param {number|null} score * @param {string} scoreDisplayMode - * @return {!Element} + * @return {Element} */ _setRatingClass(element, score, scoreDisplayMode) { const rating = Util.calculateRating(score, scoreDisplayMode); @@ -85,8 +93,8 @@ class CategoryRenderer { } /** - * @param {!ReportRenderer.CategoryJSON} category - * @return {!Element} + * @param {CategoryJSON} category + * @return {Element} */ renderCategoryHeader(category) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-category-header', this.templateContext); @@ -102,16 +110,15 @@ class CategoryRenderer { this.dom.find('.lh-category-header__description', tmpl).appendChild(descEl); } - - return /** @type {!Element} */ (tmpl.firstElementChild); + return /** @type {Element} */ (tmpl.firstElementChild); } /** * Renders the group container for a group of audits. Individual audit elements can be added * directly to the returned element. - * @param {!ReportRenderer.GroupJSON} group - * @param {{expandable: boolean, itemCount: (number|undefined)}} opts - * @return {!Element} + * @param {GroupJSON} group + * @param {{expandable: boolean, itemCount?: number}} opts + * @return {Element} */ renderAuditGroup(group, opts) { const expandable = opts.expandable; @@ -140,7 +147,7 @@ class CategoryRenderer { /** * Find the total number of audits contained within a section. * Accounts for nested subsections like Accessibility. - * @param {!Array} elements + * @param {Array} elements * @return {number} */ _getTotalAuditsLength(elements) { @@ -158,8 +165,8 @@ class CategoryRenderer { } /** - * @param {!Array} elements - * @return {!Element} + * @param {Array} elements + * @return {Element} */ _renderFailedAuditsSection(elements) { const failedElem = this.dom.createElement('div'); @@ -169,8 +176,8 @@ class CategoryRenderer { } /** - * @param {!Array} elements - * @return {!Element} + * @param {Array} elements + * @return {Element} */ renderPassedAuditsSection(elements) { const passedElem = this.renderAuditGroup({ @@ -182,8 +189,8 @@ class CategoryRenderer { } /** - * @param {!Array} elements - * @return {!Element} + * @param {Array} elements + * @return {Element} */ _renderNotApplicableAuditsSection(elements) { const notApplicableElem = this.renderAuditGroup({ @@ -195,9 +202,9 @@ class CategoryRenderer { } /** - * @param {!Array} manualAudits + * @param {Array} manualAudits * @param {string} manualDescription - * @return {!Element} + * @return {Element} */ _renderManualAudits(manualAudits, manualDescription) { const group = {title: 'Additional items to manually check', description: manualDescription}; @@ -211,7 +218,7 @@ class CategoryRenderer { } /** - * @param {!Document|!Element} context + * @param {ParentNode} context */ setTemplateContext(context) { this.templateContext = context; @@ -219,12 +226,12 @@ class CategoryRenderer { } /** - * @param {!ReportRenderer.CategoryJSON} category - * @return {!DocumentFragment} + * @param {CategoryJSON} category + * @return {DocumentFragment} */ renderScoreGauge(category) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge', this.templateContext); - const wrapper = this.dom.find('.lh-gauge__wrapper', tmpl); + const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge__wrapper', tmpl)); wrapper.href = `#${category.id}`; wrapper.classList.add(`lh-gauge__wrapper--${Util.calculateRating(category.score)}`); @@ -234,11 +241,15 @@ class CategoryRenderer { // 329 is ~= 2 * Math.PI * gauge radius (53) // https://codepen.io/xgad/post/svg-radial-progress-meters // score of 50: `stroke-dasharray: 164.5 329`; - this.dom.find('.lh-gauge-arc', gauge).style.strokeDasharray = `${numericScore * 329} 329`; + /** @type {?SVGCircleElement} */ + const gaugeArc = gauge.querySelector('.lh-gauge-arc'); + if (gaugeArc) { + gaugeArc.style.strokeDasharray = `${numericScore * 329} 329`; + } const scoreOutOf100 = Math.round(numericScore * 100); const percentageEl = this.dom.find('.lh-gauge__percentage', tmpl); - percentageEl.textContent = scoreOutOf100; + percentageEl.textContent = scoreOutOf100.toString(); if (category.score === null) { percentageEl.textContent = '?'; percentageEl.title = 'Errors occurred while auditing'; @@ -249,9 +260,9 @@ class CategoryRenderer { } /** - * @param {!ReportRenderer.CategoryJSON} category - * @param {!Object} groupDefinitions - * @return {!Element} + * @param {CategoryJSON} category + * @param {Object} groupDefinitions + * @return {Element} */ render(category, groupDefinitions) { const element = this.dom.createElement('div', 'lh-category'); @@ -262,10 +273,8 @@ class CategoryRenderer { const manualAudits = auditRefs.filter(audit => audit.result.scoreDisplayMode === 'manual'); const nonManualAudits = auditRefs.filter(audit => !manualAudits.includes(audit)); - const auditsGroupedByGroup = /** @type {!Object, - failed: !Array, - notApplicable: !Array}>} */ ({}); + /** @type {Object, failed: Array, notApplicable: Array}>} */ + const auditsGroupedByGroup = {}; const auditsUngrouped = {passed: [], failed: [], notApplicable: []}; nonManualAudits.forEach(auditRef => { @@ -293,15 +302,15 @@ class CategoryRenderer { } }); - const failedElements = /** @type {!Array} */ ([]); - const passedElements = /** @type {!Array} */ ([]); - const notApplicableElements = /** @type {!Array} */ ([]); + const failedElements = /** @type {Array} */ ([]); + const passedElements = /** @type {Array} */ ([]); + const notApplicableElements = /** @type {Array} */ ([]); - auditsUngrouped.failed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + auditsUngrouped.failed.forEach((/** @type {AuditJSON} */ audit, i) => failedElements.push(this.renderAudit(audit, i))); - auditsUngrouped.passed.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + auditsUngrouped.passed.forEach((/** @type {AuditJSON} */ audit, i) => passedElements.push(this.renderAudit(audit, i))); - auditsUngrouped.notApplicable.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) => + auditsUngrouped.notApplicable.forEach((/** @type {AuditJSON} */ audit, i) => notApplicableElements.push(this.renderAudit(audit, i))); Object.keys(auditsGroupedByGroup).forEach(groupId => { @@ -312,7 +321,6 @@ class CategoryRenderer { const auditGroupElem = this.renderAuditGroup(group, {expandable: false}); groups.failed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); auditGroupElem.classList.add('lh-audit-group--unadorned'); - auditGroupElem.open = true; failedElements.push(auditGroupElem); } @@ -357,7 +365,7 @@ class CategoryRenderer { /** * Create a non-semantic span used for hash navigation of categories - * @param {!Element} element + * @param {Element} element * @param {string} id */ createPermalinkSpan(element, id) { diff --git a/lighthouse-core/report/html/renderer/crc-details-renderer.js b/lighthouse-core/report/html/renderer/crc-details-renderer.js index 7960b46bf8c9..5c5fe7957421 100644 --- a/lighthouse-core/report/html/renderer/crc-details-renderer.js +++ b/lighthouse-core/report/html/renderer/crc-details-renderer.js @@ -12,11 +12,13 @@ /* globals self Util */ +/** @typedef {import('./dom.js')} DOM */ + class CriticalRequestChainRenderer { /** * Create render context for critical-request-chain tree display. - * @param {!Object} tree - * @return {{tree: !Object, startTime: number, transferSize: number}} + * @param {LH.Audit.SimpleCriticalRequestNode} tree + * @return {{tree: LH.Audit.SimpleCriticalRequestNode, startTime: number, transferSize: number}} */ static initTree(tree) { let startTime = 0; @@ -34,13 +36,13 @@ class CriticalRequestChainRenderer { * parent. Calculates if this node is the last child, whether it has any * children itself and what the tree looks like all the way back up to the root, * so the tree markers can be drawn correctly. - * @param {!Object} parent + * @param {LH.Audit.SimpleCriticalRequestNode} parent * @param {string} id * @param {number} startTime * @param {number} transferSize - * @param {!Array=} treeMarkers + * @param {Array=} treeMarkers * @param {boolean=} parentIsLastChild - * @return {!CriticalRequestChainRenderer.CRCSegment} + * @return {CRCSegment} */ static createSegment(parent, id, startTime, transferSize, treeMarkers, parentIsLastChild) { const node = parent[id]; @@ -68,10 +70,10 @@ class CriticalRequestChainRenderer { /** * Creates the DOM for a tree segment. - * @param {!DOM} dom - * @param {!DocumentFragment} tmpl - * @param {!CriticalRequestChainRenderer.CRCSegment} segment - * @return {!Node} + * @param {DOM} dom + * @param {DocumentFragment} tmpl + * @param {CRCSegment} segment + * @return {Node} */ static createChainNode(dom, tmpl, segment) { const chainsEl = dom.cloneTemplate('#tmpl-lh-crc__chains', tmpl); @@ -128,11 +130,11 @@ class CriticalRequestChainRenderer { /** * Recursively builds a tree from segments. - * @param {!DOM} dom - * @param {!DocumentFragment} tmpl - * @param {!CriticalRequestChainRenderer.CRCSegment} segment - * @param {!Element} elem Parent element. - * @param {!CriticalRequestChainRenderer.CRCDetailsJSON} details + * @param {DOM} dom + * @param {DocumentFragment} tmpl + * @param {CRCSegment} segment + * @param {Element} elem Parent element. + * @param {CRCDetailsJSON} details */ static buildTree(dom, tmpl, segment, elem, details) { elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment)); @@ -145,10 +147,10 @@ class CriticalRequestChainRenderer { } /** - * @param {!DOM} dom - * @param {!Node} templateContext - * @param {!CriticalRequestChainRenderer.CRCDetailsJSON} details - * @return {!Element} + * @param {DOM} dom + * @param {ParentNode} templateContext + * @param {CRCDetailsJSON} details + * @return {Element} */ static render(dom, templateContext, details) { const tmpl = dom.cloneTemplate('#tmpl-lh-crc', templateContext); @@ -157,7 +159,7 @@ class CriticalRequestChainRenderer { // Fill in top summary. dom.find('.lh-crc__longest_duration', tmpl).textContent = Util.formatNumber(details.longestChain.duration) + 'ms'; - dom.find('.lh-crc__longest_length', tmpl).textContent = details.longestChain.length; + dom.find('.lh-crc__longest_length', tmpl).textContent = details.longestChain.length.toString(); dom.find('.lh-crc__longest_transfersize', tmpl).textContent = Util.formatBytesToKB(details.longestChain.transferSize); @@ -181,44 +183,19 @@ if (typeof module !== 'undefined' && module.exports) { } /** @typedef {{ - * type: string, - * header: {text: string}, - * longestChain: {duration: number, length: number, transferSize: number}, - * chains: !Object - * }} + type: string, + header: {text: string}, + longestChain: {duration: number, length: number, transferSize: number}, + chains: LH.Audit.SimpleCriticalRequestNode + }} CRCDetailsJSON */ -CriticalRequestChainRenderer.CRCDetailsJSON; // eslint-disable-line no-unused-expressions - -/** @typedef {{ - * endTime: number, - * responseReceivedTime: number, - * startTime: number, - * transferSize: number, - * url: string - * }} - */ -CriticalRequestChainRenderer.CRCRequest; // eslint-disable-line no-unused-expressions - -/** - * Record type so children can circularly have CRCNode values. - * @struct - * @record - */ -CriticalRequestChainRenderer.CRCNode = function() {}; - -/** @type {!Object} */ -CriticalRequestChainRenderer.CRCNode.prototype.children; // eslint-disable-line no-unused-expressions - -/** @type {!CriticalRequestChainRenderer.CRCRequest} */ -CriticalRequestChainRenderer.CRCNode.prototype.request; // eslint-disable-line no-unused-expressions /** @typedef {{ - * node: !CriticalRequestChainRenderer.CRCNode, - * isLastChild: boolean, - * hasChildren: boolean, - * startTime: number, - * transferSize: number, - * treeMarkers: !Array - * }} + node: LH.Audit.SimpleCriticalRequestNode[string], + isLastChild: boolean, + hasChildren: boolean, + startTime: number, + transferSize: number, + treeMarkers: Array + }} CRCSegment */ -CriticalRequestChainRenderer.CRCSegment; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/html/renderer/details-renderer.js b/lighthouse-core/report/html/renderer/details-renderer.js index e141ec390080..b9082090c0d5 100644 --- a/lighthouse-core/report/html/renderer/details-renderer.js +++ b/lighthouse-core/report/html/renderer/details-renderer.js @@ -7,54 +7,61 @@ /* globals self CriticalRequestChainRenderer Util URL */ +/** @typedef {import('./dom.js')} DOM */ +/** @typedef {import('./crc-details-renderer.js')} CRCDetailsJSON */ + class DetailsRenderer { /** - * @param {!DOM} dom + * @param {DOM} dom */ constructor(dom) { - /** @private {!DOM} */ + /** @type {DOM} */ this._dom = dom; - /** @private {!Document|!Element} */ + /** @type {ParentNode} */ this._templateContext; // eslint-disable-line no-unused-expressions } /** - * @param {!Document|!Element} context + * @param {ParentNode} context */ setTemplateContext(context) { this._templateContext = context; } /** - * @param {!DetailsRenderer.DetailsJSON} details - * @return {!Element} + * @param {DetailsJSON} details + * @return {Element} */ render(details) { switch (details.type) { case 'text': - return this._renderText(/** @type {!DetailsRenderer.StringDetailsJSON} */ (details)); + return this._renderText(/** @type {StringDetailsJSON} */ (details)); case 'url': - return this._renderTextURL(/** @type {!DetailsRenderer.StringDetailsJSON} */ (details)); + return this._renderTextURL(/** @type {StringDetailsJSON} */ (details)); case 'bytes': - return this._renderBytes(/** @type {!DetailsRenderer.NumericUnitDetailsJSON} */ (details)); + return this._renderBytes(/** @type {NumericUnitDetailsJSON} */ (details)); case 'ms': // eslint-disable-next-line max-len - return this._renderMilliseconds(/** @type {!DetailsRenderer.NumericUnitDetailsJSON} */ (details)); + return this._renderMilliseconds(/** @type {NumericUnitDetailsJSON} */ (details)); case 'link': - return this._renderLink(/** @type {!DetailsRenderer.LinkDetailsJSON} */ (details)); + // @ts-ignore - TODO(bckenny): Fix type hierarchy + return this._renderLink(/** @type {LinkDetailsJSON} */ (details)); case 'thumbnail': - return this._renderThumbnail(/** @type {!DetailsRenderer.ThumbnailDetails} */ (details)); + return this._renderThumbnail(/** @type {ThumbnailDetails} */ (details)); case 'filmstrip': - return this._renderFilmstrip(/** @type {!DetailsRenderer.FilmstripDetails} */ (details)); + // @ts-ignore - TODO(bckenny): Fix type hierarchy + return this._renderFilmstrip(/** @type {FilmstripDetails} */ (details)); case 'table': - return this._renderTable(/** @type {!DetailsRenderer.TableDetailsJSON} */ (details)); + // @ts-ignore - TODO(bckenny): Fix type hierarchy + return this._renderTable(/** @type {TableDetailsJSON} */ (details)); case 'code': return this._renderCode(details); case 'node': - return this.renderNode(/** @type {!DetailsRenderer.NodeDetailsJSON} */(details)); + return this.renderNode(/** @type {NodeDetailsJSON} */(details)); case 'criticalrequestchain': return CriticalRequestChainRenderer.render(this._dom, this._templateContext, - /** @type {!CriticalRequestChainRenderer.CRCDetailsJSON} */ (details)); + // @ts-ignore - TODO(bckenny): Fix type hierarchy + /** @type {CRCDetailsJSON} */ (details)); default: { throw new Error(`Unknown type: ${details.type}`); } @@ -62,8 +69,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.NumericUnitDetailsJSON} details - * @return {!Element} + * @param {NumericUnitDetailsJSON} details + * @return {Element} */ _renderBytes(details) { // TODO: handle displayUnit once we have something other than 'kb' @@ -72,8 +79,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.NumericUnitDetailsJSON} details - * @return {!Element} + * @param {NumericUnitDetailsJSON} details + * @return {Element} */ _renderMilliseconds(details) { let value = Util.formatMilliseconds(details.value, details.granularity); @@ -85,8 +92,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.StringDetailsJSON} text - * @return {!Element} + * @param {StringDetailsJSON} text + * @return {HTMLElement} */ _renderTextURL(text) { const url = text.value; @@ -106,7 +113,7 @@ class DetailsRenderer { displayedPath = url; } - const element = this._dom.createElement('div', 'lh-text__url'); + const element = /** @type {HTMLElement} */ (this._dom.createElement('div', 'lh-text__url')); element.appendChild(this._renderText({ value: displayedPath, type: 'text', @@ -126,8 +133,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.LinkDetailsJSON} details - * @return {!Element} + * @param {LinkDetailsJSON} details + * @return {Element} */ _renderLink(details) { const allowedProtocols = ['https:', 'http:']; @@ -140,7 +147,7 @@ class DetailsRenderer { }); } - const a = /** @type {!HTMLAnchorElement} */ (this._dom.createElement('a')); + const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a')); a.rel = 'noopener'; a.target = '_blank'; a.textContent = details.text; @@ -150,8 +157,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.StringDetailsJSON} text - * @return {!Element} + * @param {StringDetailsJSON} text + * @return {Element} */ _renderText(text) { const element = this._dom.createElement('div', 'lh-text'); @@ -162,20 +169,23 @@ class DetailsRenderer { /** * Create small thumbnail with scaled down image asset. * If the supplied details doesn't have an image/* mimeType, then an empty span is returned. - * @param {!DetailsRenderer.ThumbnailDetails} details - * @return {!Element} + * @param {ThumbnailDetails} details + * @return {Element} */ _renderThumbnail(details) { - const element = this._dom.createElement('img', 'lh-thumbnail'); - element.src = details.value; - element.title = details.value; + const element = /** @type {HTMLImageElement}*/ (this._dom.createElement('img', 'lh-thumbnail')); + /** @type {string} */ + // @ts-ignore - type should have a value if we get here. + const strValue = details.value; + element.src = strValue; + element.title = strValue; element.alt = ''; return element; } /** - * @param {!DetailsRenderer.TableDetailsJSON} details - * @return {!Element} + * @param {TableDetailsJSON} details + * @return {Element} */ _renderTable(details) { if (!details.items.length) return this._dom.createElement('span'); @@ -197,15 +207,18 @@ class DetailsRenderer { for (const row of details.items) { const rowElem = this._dom.createChildOf(tbodyElem, 'tr'); for (const heading of details.headings) { - const value = /** @type {number|string|!DetailsRenderer.DetailsJSON} */ (row[heading.key]); + const key = /** @type {keyof DetailsJSON} */ (heading.key); + // TODO(bckenny): type should be naturally inferred here. + const value = /** @type {number|string|DetailsJSON|undefined} */ (row[key]); if (typeof value === 'undefined' || value === null) { this._dom.createChildOf(rowElem, 'td', 'lh-table-column--empty'); continue; } // handle nested types like code blocks in table rows. + // @ts-ignore - TODO(bckenny): narrow first if (value.type) { - const valueAsDetails = /** @type {!DetailsRenderer.DetailsJSON} */ (value); + const valueAsDetails = /** @type {DetailsJSON} */ (value); const classes = `lh-table-column--${valueAsDetails.type}`; this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(valueAsDetails)); continue; @@ -218,7 +231,11 @@ class DetailsRenderer { displayUnit: heading.displayUnit, granularity: heading.granularity, }; - const classes = `lh-table-column--${value.type || heading.itemType}`; + + /** @type {string|undefined} */ + // @ts-ignore - TODO(bckenny): handle with refactoring above + const valueType = value.type; + const classes = `lh-table-column--${valueType || heading.itemType}`; this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(item)); } } @@ -226,14 +243,18 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.NodeDetailsJSON} item - * @return {!Element} + * @param {NodeDetailsJSON} item + * @return {Element} * @protected */ renderNode(item) { - const element = this._dom.createElement('span', 'lh-node'); - element.textContent = item.snippet; - element.title = item.selector; + const element = /** @type {HTMLSpanElement} */ (this._dom.createElement('span', 'lh-node')); + if (item.snippet) { + element.textContent = item.snippet; + } + if (item.selector) { + element.title = item.selector; + } if (item.path) element.setAttribute('data-path', item.path); if (item.selector) element.setAttribute('data-selector', item.selector); if (item.snippet) element.setAttribute('data-snippet', item.snippet); @@ -241,8 +262,8 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.FilmstripDetails} details - * @return {!Element} + * @param {FilmstripDetails} details + * @return {Element} */ _renderFilmstrip(details) { const filmstripEl = this._dom.createElement('div', 'lh-filmstrip'); @@ -258,12 +279,12 @@ class DetailsRenderer { } /** - * @param {!DetailsRenderer.DetailsJSON} details - * @return {!Element} + * @param {DetailsJSON} details + * @return {Element} */ _renderCode(details) { const pre = this._dom.createElement('pre', 'lh-code'); - pre.textContent = details.value; + pre.textContent = /** @type {string} */ (details.value); return pre; } } @@ -277,92 +298,81 @@ if (typeof module !== 'undefined' && module.exports) { // TODO, what's the diff between DetailsJSON and NumericUnitDetailsJSON? /** * @typedef {{ - * type: string, - * value: (string|number|undefined), - * summary: (DetailsRenderer.OpportunitySummary|undefined), - * granularity: (number|undefined), - * displayUnit: (string|undefined) - * }} + type: string, + value: (string|number|undefined), + summary?: OpportunitySummary, + granularity?: number, + displayUnit?: string + }} DetailsJSON */ -DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * type: string, - * value: string, - * granularity: (number|undefined), - * displayUnit: (string|undefined), - * }} + type: string, + value: string, + granularity?: number, + displayUnit?: string, + }} StringDetailsJSON */ -DetailsRenderer.StringDetailsJSON; // eslint-disable-line no-unused-expressions - /** * @typedef {{ - * type: string, - * value: number, - * granularity: (number|undefined), - * displayUnit: (string|undefined), - * }} + type: string, + value: number, + granularity?: number, + displayUnit?: string, + }} NumericUnitDetailsJSON */ -DetailsRenderer.NumericUnitDetailsJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * type: string, - * path: (string|undefined), - * selector: (string|undefined), - * snippet:(string|undefined) - * }} + type: string, + path?: string, + selector?: string, + snippet?: string + }} NodeDetailsJSON */ -DetailsRenderer.NodeDetailsJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * itemType: string, - * key: string, - * text: (string|undefined), - * granularity: (number|undefined), - * displayUnit: (string|undefined), - * }} + itemType: string, + key: string, + text?: string, + granularity?: number, + displayUnit?: string, + }} TableHeaderJSON */ -DetailsRenderer.TableHeaderJSON; // eslint-disable-line no-unused-expressions /** @typedef {{ - * type: string, - * items: !Array, - * headings: !Array - * }} + type: string, + items: Array, + headings: Array + }} TableDetailsJSON */ -DetailsRenderer.TableDetailsJSON; // eslint-disable-line no-unused-expressions /** @typedef {{ - * type: string, - * value: (string|undefined), - * }} + type: string, + value?: string, + }} ThumbnailDetails */ -DetailsRenderer.ThumbnailDetails; // eslint-disable-line no-unused-expressions /** @typedef {{ - * type: string, - * text: string, - * url: string - * }} + type: string, + text: string, + url: string + }} LinkDetailsJSON */ -DetailsRenderer.LinkDetailsJSON; // eslint-disable-line no-unused-expressions /** @typedef {{ - * type: string, - * scale: number, - * items: !Array<{timing: number, timestamp: number, data: string}>, - * }} + type: string, + scale: number, + items: Array<{timing: number, timestamp: number, data: string}>, + }} FilmstripDetails */ -DetailsRenderer.FilmstripDetails; // eslint-disable-line no-unused-expressions /** @typedef {{ - * wastedMs: (number|undefined), - * wastedBytes: (number|undefined), - * }} + wastedMs?: number, + wastedBytes?: number + }} OpportunitySummary */ -DetailsRenderer.OpportunitySummary; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/html/renderer/dom.js b/lighthouse-core/report/html/renderer/dom.js index c60f57aeb437..f6905b2af8ec 100644 --- a/lighthouse-core/report/html/renderer/dom.js +++ b/lighthouse-core/report/html/renderer/dom.js @@ -9,20 +9,21 @@ class DOM { /** - * @param {!Document} document + * @param {Document} document */ constructor(document) { - /** @private {!Document} */ + /** @type {Document} */ this._document = document; } + // TODO(bckenny): can pass along `createElement`'s inferred type /** * @param {string} name * @param {string=} className - * @param {!Object=} attrs Attribute key/val pairs. + * @param {Object=} attrs Attribute key/val pairs. * Note: if an attribute key has an undefined value, this method does not * set the attribute on the node. - * @return {!Element} + * @return {Element} */ createElement(name, className, attrs = {}) { const element = this._document.createElement(name); @@ -39,20 +40,20 @@ class DOM { } /** - * @return {!DocumentFragment} + * @return {DocumentFragment} */ createFragment() { return this._document.createDocumentFragment(); } /** - * @param {!Element} parentElem + * @param {Element} parentElem * @param {string} elementName * @param {string=} className - * @param {!Object=} attrs Attribute key/val pairs. + * @param {Object=} attrs Attribute key/val pairs. * Note: if an attribute key has an undefined value, this method does not * set the attribute on the node. - * @return {!Element} + * @return {Element} */ createChildOf(parentElem, elementName, className, attrs) { const element = this.createElement(elementName, className, attrs); @@ -62,8 +63,8 @@ class DOM { /** * @param {string} selector - * @param {!Node} context - * @return {!DocumentFragment} A clone of the template content. + * @param {ParentNode} context + * @return {DocumentFragment} A clone of the template content. * @throws {Error} */ cloneTemplate(selector, context) { @@ -72,15 +73,14 @@ class DOM { throw new Error(`Template not found: template${selector}`); } - const clone = /** @type {!DocumentFragment} */ (this._document.importNode( - template.content, true)); + const clone = this._document.importNode(template.content, true); // Prevent duplicate styles in the DOM. After a template has been stamped // for the first time, remove the clone's styles so they're not re-added. if (template.hasAttribute('data-stamped')) { this.findAll('style', clone).forEach(style => style.remove()); } - template.setAttribute('data-stamped', true); + template.setAttribute('data-stamped', 'true'); return clone; } @@ -96,7 +96,7 @@ class DOM { /** * @param {string} text - * @return {!Element} + * @return {Element} */ convertMarkdownLinkSnippets(text) { const element = this.createElement('span'); @@ -111,7 +111,7 @@ class DOM { // Append link if there are any. if (linkText && linkHref) { - const a = /** @type {!HTMLAnchorElement} */ (this.createElement('a')); + const a = /** @type {HTMLAnchorElement} */ (this.createElement('a')); a.rel = 'noopener'; a.target = '_blank'; a.textContent = linkText; @@ -125,7 +125,7 @@ class DOM { /** * @param {string} text - * @return {!Element} + * @return {Element} */ convertMarkdownCodeSnippets(text) { const element = this.createElement('span'); @@ -136,7 +136,7 @@ class DOM { const [preambleText, codeText] = parts.splice(0, 2); element.appendChild(this._document.createTextNode(preambleText)); if (codeText) { - const pre = /** @type {!HTMLPreElement} */ (this.createElement('code')); + const pre = /** @type {HTMLPreElement} */ (this.createElement('code')); pre.textContent = codeText; element.appendChild(pre); } @@ -146,7 +146,7 @@ class DOM { } /** - * @return {!Document} + * @return {Document} */ document() { return this._document; @@ -156,10 +156,11 @@ class DOM { * Guaranteed context.querySelector. Always returns an element or throws if * nothing matches query. * @param {string} query - * @param {!Node} context - * @return {!Element} + * @param {ParentNode} context + * @return {HTMLElement} */ find(query, context) { + /** @type {?HTMLElement} */ const result = context.querySelector(query); if (result === null) { throw new Error(`query ${query} not found`); @@ -170,8 +171,8 @@ class DOM { /** * Helper for context.querySelectorAll. Returns an Array instead of a NodeList. * @param {string} query - * @param {!Node} context - * @return {!Array} + * @param {ParentNode} context + * @return {Array} */ findAll(query, context) { return Array.from(context.querySelectorAll(query)); diff --git a/lighthouse-core/report/html/renderer/logger.js b/lighthouse-core/report/html/renderer/logger.js index 718cd8c1d14a..2ee85b4a4638 100644 --- a/lighthouse-core/report/html/renderer/logger.js +++ b/lighthouse-core/report/html/renderer/logger.js @@ -10,13 +10,12 @@ */ class Logger { /** - * @param {!Element} element + * @param {Element} element */ constructor(element) { - /** @type {!Element} */ + /** @type {Element} */ this.el = element; - /** @private {?number} */ - this._id = null; + this._id = undefined; } /** @@ -26,7 +25,7 @@ class Logger { * Default is true. */ log(msg, autoHide = true) { - clearTimeout(this._id); + this._id && clearTimeout(this._id); this.el.textContent = msg; this.el.classList.add('show'); @@ -55,7 +54,7 @@ class Logger { * Explicitly hides the butter bar. */ hide() { - clearTimeout(this._id); + this._id && clearTimeout(this._id); this.el.classList.remove('show'); } } diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js index db9ef6e41751..abfb66a57a61 100644 --- a/lighthouse-core/report/html/renderer/performance-category-renderer.js +++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js @@ -7,10 +7,17 @@ /* globals self, Util, CategoryRenderer */ +/** @typedef {import('./dom.js')} DOM */ +/** @typedef {import('./report-renderer.js').CategoryJSON} CategoryJSON */ +/** @typedef {import('./report-renderer.js').GroupJSON} GroupJSON */ +/** @typedef {import('./report-renderer.js').AuditJSON} AuditJSON */ +/** @typedef {import('./details-renderer.js').OpportunitySummary} OpportunitySummary */ +/** @typedef {import('./details-renderer.js').FilmstripDetails} FilmstripDetails */ + class PerformanceCategoryRenderer extends CategoryRenderer { /** - * @param {!ReportRenderer.AuditJSON} audit - * @return {!Element} + * @param {AuditJSON} audit + * @return {Element} */ _renderMetric(audit) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-metric', this.templateContext); @@ -39,10 +46,10 @@ class PerformanceCategoryRenderer extends CategoryRenderer { } /** - * @param {!ReportRenderer.AuditJSON} audit + * @param {AuditJSON} audit * @param {number} index * @param {number} scale - * @return {!Element} + * @return {Element} */ _renderOpportunity(audit, index, scale) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity', this.templateContext); @@ -56,13 +63,15 @@ class PerformanceCategoryRenderer extends CategoryRenderer { if (audit.result.errorMessage || audit.result.explanation) { const debugStrEl = this.dom.createChildOf(titleEl, 'div', 'lh-debug'); - debugStrEl.textContent = audit.result.errorMessage || audit.result.explanation; + debugStrEl.textContent = audit.result.errorMessage || audit.result.explanation || null; } if (audit.result.scoreDisplayMode === 'error') return element; const details = audit.result.details; - const summaryInfo = /** @type {!DetailsRenderer.OpportunitySummary} - */ (details && details.summary); + if (!details) { + return element; + } + const summaryInfo = /** @type {OpportunitySummary} */ (details.summary); if (!summaryInfo || !summaryInfo.wastedMs) { return element; } @@ -91,7 +100,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { * Get an audit's wastedMs to sort the opportunity by, and scale the sparkline width * Opportunties with an error won't have a summary object, so MIN_VALUE is returned to keep any * erroring opportunities last in sort order. - * @param {!ReportRenderer.AuditJSON} audit + * @param {AuditJSON} audit * @return {number} */ _getWastedMs(audit) { @@ -107,6 +116,9 @@ class PerformanceCategoryRenderer extends CategoryRenderer { } /** + * @param {CategoryJSON} category + * @param {Object} groups + * @return {Element} * @override */ render(category, groups) { @@ -135,7 +147,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { 'lh-metrics__disclaimer lh-metrics__disclaimer'); estValuesEl.textContent = 'Values are estimated and may vary.'; - metricAuditsEl.open = true; metricAuditsEl.classList.add('lh-audit-group--metrics'); element.appendChild(metricAuditsEl); @@ -145,9 +156,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer { const thumbnailResult = thumbnailAudit && thumbnailAudit.result; if (thumbnailResult && thumbnailResult.details) { timelineEl.id = thumbnailResult.name; - const thumbnailDetails = /** @type {!DetailsRenderer.FilmstripDetails} */ - (thumbnailResult.details); - const filmstripEl = this.detailsRenderer.render(thumbnailDetails); + const filmstripEl = this.detailsRenderer.render(thumbnailResult.details); timelineEl.appendChild(filmstripEl); } @@ -166,7 +175,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { groupEl.appendChild(headerEl); opportunityAudits.forEach((item, i) => groupEl.appendChild(this._renderOpportunity(item, i, scale))); - groupEl.open = true; groupEl.classList.add('lh-audit-group--opportunities'); element.appendChild(groupEl); } @@ -183,7 +191,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer { if (diagnosticAudits.length) { const groupEl = this.renderAuditGroup(groups['diagnostics'], {expandable: false}); diagnosticAudits.forEach((item, i) => groupEl.appendChild(this.renderAudit(item, i))); - groupEl.open = true; groupEl.classList.add('lh-audit-group--diagnostics'); element.appendChild(groupEl); } diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js index e75abe523ab1..d9cb513edef5 100644 --- a/lighthouse-core/report/html/renderer/report-renderer.js +++ b/lighthouse-core/report/html/renderer/report-renderer.js @@ -12,26 +12,29 @@ * Dummy text for ensuring report robustness: pre$`post %%LIGHTHOUSE_JSON%% */ +/** @typedef {import('./dom.js')} DOM */ +/** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */ + /* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer */ class ReportRenderer { /** - * @param {!DOM} dom + * @param {DOM} dom */ constructor(dom) { - /** @private {!DOM} */ + /** @type {DOM} */ this._dom = dom; - /** @private {!Document|!Element} */ + /** @type {ParentNode} */ this._templateContext = this._dom.document(); } /** - * @param {!ReportRenderer.ReportJSON} report - * @param {!Element} container Parent element to render the report into. + * @param {ReportJSON} report + * @param {Element} container Parent element to render the report into. */ renderReport(report, container) { // If any mutations happen to the report within the renderers, we want the original object untouched - const clone = /** @type {!ReportRenderer.ReportJSON} */ (JSON.parse(JSON.stringify(report))); + const clone = /** @type {ReportJSON} */ (JSON.parse(JSON.stringify(report))); // TODO(phulce): we all agree this is technical debt we should fix if (typeof clone.categories !== 'object') throw new Error('No categories provided.'); @@ -40,31 +43,31 @@ class ReportRenderer { container.textContent = ''; // Remove previous report. container.appendChild(this._renderReport(clone)); - return /** @type {!Element} **/ (container); + return /** @type {Element} **/ (container); } /** * Define a custom element for to be extracted from. For example: * this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html')) - * @param {!Document|!Element} context + * @param {ParentNode} context */ setTemplateContext(context) { this._templateContext = context; } /** - * @param {!ReportRenderer.ReportJSON} report - * @return {!DocumentFragment} + * @param {ReportJSON} report + * @return {DocumentFragment} */ _renderReportHeader(report) { const header = this._dom.cloneTemplate('#tmpl-lh-heading', this._templateContext); this._dom.find('.lh-config__timestamp', header).textContent = Util.formatDateTime(report.fetchTime); this._dom.find('.lh-product-info__version', header).textContent = report.lighthouseVersion; - const url = this._dom.find('.lh-metadata__url', header); + const url = /** @type {HTMLAnchorElement} */ (this._dom.find('.lh-metadata__url', header)); url.href = report.finalUrl; url.textContent = report.finalUrl; - const toolbarUrl = this._dom.find('.lh-toolbar__url', header); + const toolbarUrl = /** @type {HTMLAnchorElement}*/ (this._dom.find('.lh-toolbar__url', header)); toolbarUrl.href = report.finalUrl; toolbarUrl.textContent = report.finalUrl; @@ -83,8 +86,8 @@ class ReportRenderer { } /** - * @param {!ReportRenderer.ReportJSON} report - * @return {!DocumentFragment} + * @param {ReportJSON} report + * @return {DocumentFragment} */ _renderReportFooter(report) { const footer = this._dom.cloneTemplate('#tmpl-lh-footer', this._templateContext); @@ -96,8 +99,8 @@ class ReportRenderer { /** * Returns a div with a list of top-level warnings, or an empty div if no warnings. - * @param {!ReportRenderer.ReportJSON} report - * @return {!Node} + * @param {ReportJSON} report + * @return {Node} */ _renderReportWarnings(report) { if (!report.runWarnings || report.runWarnings.length === 0) { @@ -115,8 +118,8 @@ class ReportRenderer { } /** - * @param {!ReportRenderer.ReportJSON} report - * @return {!DocumentFragment} + * @param {ReportJSON} report + * @return {DocumentFragment} */ _renderReport(report) { const headerStickyContainer = this._dom.createElement('div', 'lh-header-sticky'); @@ -172,8 +175,8 @@ class ReportRenderer { /** * Place the AuditResult into the auditDfn (which has just weight & group) - * @param {!Object} audits - * @param {!Array} reportCategories + * @param {Object} audits + * @param {Array} reportCategories */ static smooshAuditResultsIntoCategories(audits, reportCategories) { for (const category of reportCategories) { @@ -193,67 +196,62 @@ if (typeof module !== 'undefined' && module.exports) { /** * @typedef {{ - * rawValue: (number|boolean|undefined), - * name: string, - * description: string, - * explanation: (string|undefined), - * errorMessage: (string|undefined), - * displayValue: (string|Array|undefined), - * helpText: string, - * scoreDisplayMode: string, - * error: boolean, - * score: (number|null), - * details: (!DetailsRenderer.DetailsJSON|undefined), - * }} + rawValue: (number|boolean|undefined), + name: string, + description: string, + explanation?: string, + errorMessage?: string, + displayValue?: string|Array, + helpText: string, + scoreDisplayMode: string, + error: boolean, + score: (number|null), + details?: DetailsJSON, + }} AuditResultJSON */ -ReportRenderer.AuditResultJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * id: string, - * score: (number|null), - * weight: number, - * group: (string|undefined), - * result: ReportRenderer.AuditResultJSON - * }} + id: string, + score: (number|null), + weight: number, + group?: string, + result: AuditResultJSON + }} AuditJSON */ -ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * title: string, - * id: string, - * score: (number|null), - * description: (string|undefined), - * manualDescription: string, - * auditRefs: !Array - * }} + title: string, + id: string, + score: (number|null), + description?: string, + manualDescription: string, + auditRefs: Array + }} CategoryJSON */ -ReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * title: string, - * description: (string|undefined), - * }} + title: string, + description?: string, + }} GroupJSON */ -ReportRenderer.GroupJSON; // eslint-disable-line no-unused-expressions /** * @typedef {{ - * lighthouseVersion: string, - * userAgent: string, - * fetchTime: string, - * timing: {total: number}, - * requestedUrl: string, - * finalUrl: string, - * runWarnings: (!Array|undefined), - * artifacts: {traces: {defaultPass: {traceEvents: !Array}}}, - * audits: !Object, - * categories: !Object, - * reportCategories: !Array, - * categoryGroups: !Object, - * configSettings: !LH.Config.Settings, - * }} + lighthouseVersion: string, + userAgent: string, + fetchTime: string, + timing: {total: number}, + requestedUrl: string, + finalUrl: string, + runWarnings?: Array, + artifacts: {traces: {defaultPass: {traceEvents: Array}}}, + audits: Object, + categories: Object, + reportCategories: Array, + categoryGroups: Object, + configSettings: LH.Config.Settings, + }} ReportJSON */ -ReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions diff --git a/lighthouse-core/report/html/renderer/report-ui-features.js b/lighthouse-core/report/html/renderer/report-ui-features.js index 600e1d7e4f7f..f54d015e75ee 100644 --- a/lighthouse-core/report/html/renderer/report-ui-features.js +++ b/lighthouse-core/report/html/renderer/report-ui-features.js @@ -12,40 +12,43 @@ /* globals self URL Blob CustomEvent getFilenamePrefix window */ +/** @typedef {import('./dom.js')} DOM */ +/** @typedef {import('./report-renderer.js').ReportJSON} ReportJSON */ + class ReportUIFeatures { /** - * @param {!DOM} dom + * @param {DOM} dom */ constructor(dom) { - /** @type {!ReportRenderer.ReportJSON} */ + /** @type {ReportJSON} */ this.json; // eslint-disable-line no-unused-expressions - /** @protected {!DOM} */ + /** @type {DOM} */ this._dom = dom; - /** @protected {!Document} */ + /** @type {Document} */ this._document = this._dom.document(); - /** @private {boolean} */ + /** @type {boolean} */ this._copyAttempt = false; - /** @type {!Element} */ + /** @type {HTMLElement} */ this.exportButton; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.headerSticky; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.headerBackground; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.lighthouseIcon; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.scoresShadowWrapper; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.productInfo; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.toolbar; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.toolbarMetadata; // eslint-disable-line no-unused-expressions - /** @type {!Element} */ + /** @type {HTMLElement} */ this.env; // eslint-disable-line no-unused-expressions - /** @type {!number} */ + /** @type {number} */ this.headerOverlap = 0; - /** @type {!number} */ + /** @type {number} */ this.headerHeight = 0; /** @type {number} */ this.latestKnownScrollY = 0; @@ -65,7 +68,7 @@ class ReportUIFeatures { /** * Adds export button, print, and other functionality to the report. The method * should be called whenever the report needs to be re-rendered. - * @param {!ReportRenderer.ReportJSON} report + * @param {ReportJSON} report */ initFeatures(report) { this.json = report; @@ -75,17 +78,18 @@ class ReportUIFeatures { this._setupHeaderAnimation(); this._resetUIState(); this._document.addEventListener('keydown', this.printShortCutDetect); + // @ts-ignore - tsc thinks document can't listen for `copy` this._document.addEventListener('copy', this.onCopy); } /** * Fires a custom DOM event on target. * @param {string} name Name of the event. - * @param {!Node=} target DOM node to fire the event on. + * @param {Node=} target DOM node to fire the event on. * @param {*=} detail Custom data to include. */ _fireEventOn(name, target = this._document, detail) { - const event = new CustomEvent(name, detail ? {detail} : null); + const event = new CustomEvent(name, detail ? {detail} : undefined); target.dispatchEvent(event); } @@ -98,7 +102,7 @@ class ReportUIFeatures { /** * Handle media query change events. - * @param {!MediaQueryList} mql + * @param {MediaQueryList} mql */ onMediaQueryChange(mql) { const root = this._dom.find('.lh-root', this._document); @@ -114,9 +118,9 @@ class ReportUIFeatures { } _setupHeaderAnimation() { - /** @type {!Element} **/ const scoresWrapper = this._dom.find('.lh-scores-wrapper', this._document); - this.headerOverlap = /** @type {!number} */ + this.headerOverlap = /** @type {number} */ + // @ts-ignore - TODO: move off CSSOM to support other browsers (scoresWrapper.computedStyleMap().get('margin-top').value); this.headerSticky = this._dom.find('.lh-header-sticky', this._document); @@ -128,6 +132,7 @@ class ReportUIFeatures { this.toolbarMetadata = this._dom.find('.lh-toolbar__metadata', this._document); this.env = this._dom.find('.lh-env', this._document); + // @ts-ignore - TODO: move off CSSOM to support other browsers this.headerHeight = this.headerBackground.computedStyleMap().get('height').value; this._document.addEventListener('scroll', this.onScroll, {passive: true}); @@ -138,7 +143,7 @@ class ReportUIFeatures { /** * Handle copy events. - * @param {!Event} e + * @param {ClipboardEvent} e */ onCopy(e) { // Only handle copy button presses (e.g. ignore the user copying page text). @@ -157,7 +162,6 @@ class ReportUIFeatures { /** * Copies the report JSON to the clipboard (if supported by the browser). - * @suppress {reportUnknownTypes} */ onCopyButtonClick() { this._fireEventOn('lh-analytics', this._document, { @@ -179,7 +183,7 @@ class ReportUIFeatures { }); } } - } catch (/** @type {!Error} */ e) { + } catch (/** @type {Error} */ e) { this._copyAttempt = false; this._fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message}); } @@ -200,7 +204,7 @@ class ReportUIFeatures { if (toggle.hasAttribute('open')) { toggle.removeAttribute('open'); } else { - toggle.setAttribute('open', true); + toggle.setAttribute('open', 'true'); } } @@ -220,15 +224,15 @@ class ReportUIFeatures { `translate3d(calc(var(--report-content-width) / 2),` + ` calc(-100% - ${animateScrollPercentage * this.headerOverlap * -1}px), 0) scale(${1 - animateScrollPercentage})`; - this.lighthouseIcon.style.opacity = Math.max(0, 1 - animateScrollPercentage); - this.scoresShadowWrapper.style.opacity = 1 - animateScrollPercentage; - const scoresContainer = this.scoresShadowWrapper.parentElement; + this.lighthouseIcon.style.opacity = Math.max(0, 1 - animateScrollPercentage).toString(); + this.scoresShadowWrapper.style.opacity = (1 - animateScrollPercentage).toString(); + const scoresContainer = /** @type {HTMLElement} */ (this.scoresShadowWrapper.parentElement); scoresContainer.style.borderRadius = (1 - animateScrollPercentage) * 8 + 'px'; scoresContainer.style.boxShadow = `0 4px 2px -2px rgba(0, 0, 0, ${animateScrollPercentage * 0.2})`; - const scoreScale = scoresContainer.querySelector('.lh-scorescale'); + const scoreScale = this._dom.find('.lh-scorescale', scoresContainer); scoreScale.style.opacity = `${1 - animateScrollPercentage}`; - const scoreHeader = scoresContainer.querySelector('.lh-scores-header'); + const scoreHeader = this._dom.find('.lh-scores-header', scoresContainer); const delta = 32 * animateScrollPercentage; scoreHeader.style.paddingBottom = `${32 - delta}px`; scoresContainer.style.marginBottom = `${delta}px`; @@ -236,8 +240,8 @@ class ReportUIFeatures { animateScrollPercentage}px)`; this.exportButton.style.transform = `scale(${1 - 0.2 * animateScrollPercentage})`; // start showing the productinfo when we are at the 50% mark of our animation - this.productInfo.style.opacity = this.toolbarMetadata.style.opacity = - animateScrollPercentage < 0.5 ? 0 : (animateScrollPercentage - 0.5) * 2; + const opacity = animateScrollPercentage < 0.5 ? 0 : (animateScrollPercentage - 0.5) * 2; + this.productInfo.style.opacity = this.toolbarMetadata.style.opacity = opacity.toString(); this.env.style.transform = `translateY(${Math.max( 0, headerTransitionHeightDiff * animateScrollPercentage - 6 @@ -253,11 +257,11 @@ class ReportUIFeatures { /** * Click handler for export button. - * @param {!Event} e + * @param {Event} e */ onExportButtonClick(e) { e.preventDefault(); - const el = /** @type {!Element} */ (e.target); + const el = /** @type {Element} */ (e.target); el.classList.toggle('active'); this._document.addEventListener('keydown', this.onKeyDown); } @@ -274,14 +278,14 @@ class ReportUIFeatures { /** * Handler for "export as" button. - * @param {!Event} e + * @param {Event} e */ onExport(e) { e.preventDefault(); - const el = /** @type {!Element} */ (e.target); + const el = /** @type {?Element} */ (e.target); - if (!el.hasAttribute('data-action')) { + if (!el || el.hasAttribute('data-action')) { return; } @@ -308,7 +312,7 @@ class ReportUIFeatures { const htmlStr = this.getReportHtml(); try { this._saveFile(new Blob([htmlStr], {type: 'text/html'})); - } catch (/** @type {!Error} */ e) { + } catch (/** @type {Error} */ e) { this._fireEventOn('lh-log', this._document, { cmd: 'error', msg: 'Could not export as HTML. ' + e.message, }); @@ -331,7 +335,7 @@ class ReportUIFeatures { /** * Keydown handler for the document. - * @param {!Event} e + * @param {KeyboardEvent} e */ onKeyDown(e) { if (e.keyCode === 27) { // ESC @@ -353,24 +357,24 @@ class ReportUIFeatures { // load event, however it is cross-domain and won't fire. Instead, listen // for a message from the target app saying "I'm open". const json = this.json; - window.addEventListener('message', function msgHandler(/** @type {!Event} */ e) { - const messageEvent = /** @type {!MessageEvent<{opened: boolean}>} */ (e); + window.addEventListener('message', function msgHandler(/** @type {Event} */ e) { + const messageEvent = /** @type {MessageEvent} */ (e); if (messageEvent.origin !== VIEWER_ORIGIN) { return; } - if (messageEvent.data.opened) { + if (popup && messageEvent.data.opened) { popup.postMessage({lhresults: json}, VIEWER_ORIGIN); window.removeEventListener('message', msgHandler); } }); - const popup = /** @type {!Window} */ (window.open(VIEWER_URL, '_blank')); + const popup = window.open(VIEWER_URL, '_blank'); } /** * Expands audit details when user prints via keyboard shortcut. - * @param {!Event} e + * @param {KeyboardEvent} e */ printShortCutDetect(e) { if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P @@ -384,7 +388,8 @@ class ReportUIFeatures { * open a `
` element. */ expandAllDetails() { - const details = this._dom.findAll('.lh-categories details', this._document); + const details = /** @type {Array} */ (this._dom.findAll( + '.lh-categories details', this._document)); details.map(detail => detail.open = true); } @@ -393,7 +398,8 @@ class ReportUIFeatures { * open a `
` element. */ collapseAllDetails() { - const details = this._dom.findAll('.lh-categories details', this._document); + const details = /** @type {Array} */ (this._dom.findAll( + '.lh-categories details', this._document)); details.map(detail => detail.open = false); } @@ -406,9 +412,10 @@ class ReportUIFeatures { if ('onbeforeprint' in self) { self.addEventListener('afterprint', this.collapseAllDetails); } else { + const win = /** @type {Window} */ (self); // Note: FF implements both window.onbeforeprint and media listeners. However, // it doesn't matchMedia doesn't fire when matching 'print'. - self.matchMedia('print').addListener(mql => { + win.matchMedia('print').addListener(mql => { if (mql.matches) { this.expandAllDetails(); } else { @@ -438,7 +445,7 @@ class ReportUIFeatures { /** * Downloads a file (blob) using a[download]. - * @param {!Blob|!File} blob The file to save. + * @param {Blob|File} blob The file to save. * @private */ _saveFile(blob) { @@ -450,7 +457,7 @@ class ReportUIFeatures { const ext = blob.type.match('json') ? '.json' : '.html'; const href = URL.createObjectURL(blob); - const a = /** @type {!HTMLAnchorElement} */ (this._dom.createElement('a')); + const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a')); a.download = `${filename}${ext}`; a.href = href; this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM. diff --git a/lighthouse-core/report/html/renderer/util.js b/lighthouse-core/report/html/renderer/util.js index bd165d23b356..e9c13398c55a 100644 --- a/lighthouse-core/report/html/renderer/util.js +++ b/lighthouse-core/report/html/renderer/util.js @@ -18,10 +18,6 @@ const RATINGS = { ERROR: {label: 'error'}, }; -/** - * @fileoverview - * @suppress {reportUnknownTypes} see https://github.com/GoogleChrome/lighthouse/pull/4778#issuecomment-373549391 - */ class Util { static get PASS_THRESHOLD() { return PASS_THRESHOLD; @@ -195,9 +191,9 @@ class Util { return 'None'; } - /** @type {!Array} */ + /** @type {Array} */ const parts = []; - const unitLabels = /** @type {!Object} */ ({ + const unitLabels = /** @type {Object} */ ({ d: 60 * 60 * 24, h: 60 * 60, m: 60, @@ -217,8 +213,8 @@ class Util { } /** - * @param {!URL} parsedUrl - * @param {{numPathParts: (number|undefined), preserveQuery: (boolean|undefined), preserveHost: (boolean|undefined)}=} options + * @param {URL} parsedUrl + * @param {{numPathParts?: number, preserveQuery?: boolean, preserveHost?: boolean}=} options * @return {string} */ static getURLDisplayName(parsedUrl, options) { @@ -378,6 +374,5 @@ class Util { if (typeof module !== 'undefined' && module.exports) { module.exports = Util; } else { - // @ts-ignore self.Util = Util; } diff --git a/package.json b/package.json index d7ecea52bf9e..d41b78716b63 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "postinstall-prepare": "^1.0.1", "puppeteer": "^1.1.1", "sinon": "^2.3.5", - "typescript": "2.9.0-dev.20180323", + "typescript": "2.9.0-dev.20180510", "vscode-chrome-debug-core": "^3.23.8", "zone.js": "^0.7.3" }, diff --git a/tsconfig.json b/tsconfig.json index 9806892ea1b3..db61aa693b0e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,15 +15,18 @@ "diagnostics": true }, "include": [ + // TODO(bckenny): unnecessary workaround until https://github.com/Microsoft/TypeScript/issues/24062 + "node_modules/@types/node/index.d.ts", + "lighthouse-cli/**/*.js", "lighthouse-core/**/*.js", - "./typings/*.d.ts" + "./typings/*.d.ts", + ], "exclude": [ "lighthouse-cli/test/**/*.js", "lighthouse-core/test/**/*.js", "lighthouse-core/closure/**/*.js", "lighthouse-core/third_party/src/**/*.js", - "lighthouse-core/report/html/renderer/**/*.js", ] } diff --git a/typings/artifacts.d.ts b/typings/artifacts.d.ts index ccbe6e204f55..53035b0813b4 100644 --- a/typings/artifacts.d.ts +++ b/typings/artifacts.d.ts @@ -4,8 +4,8 @@ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import * as parseManifest from '../lighthouse-core/lib/manifest-parser.js'; -import * as _LanternSimulator from '../lighthouse-core/lib/dependency-graph/simulator/simulator.js'; +import parseManifest = require('../lighthouse-core/lib/manifest-parser.js'); +import _LanternSimulator = require('../lighthouse-core/lib/dependency-graph/simulator/simulator.js'); import speedline = require('speedline'); type LanternSimulator = InstanceType; diff --git a/typings/config.d.ts b/typings/config.d.ts index 2b7a3331f44f..7f4b7b6dd1d6 100644 --- a/typings/config.d.ts +++ b/typings/config.d.ts @@ -4,8 +4,9 @@ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import * as Gatherer from '../lighthouse-core/gather/gatherers/gatherer.js'; -import * as Audit from '../lighthouse-core/audits/audit.js'; +import Gatherer = require('../lighthouse-core/gather/gatherers/gatherer.js'); +import Audit = require('../lighthouse-core/audits/audit.js'); + declare global { module LH { diff --git a/typings/gatherer.d.ts b/typings/gatherer.d.ts index ff9827cc37d7..8c4e5a30f9f0 100644 --- a/typings/gatherer.d.ts +++ b/typings/gatherer.d.ts @@ -4,11 +4,11 @@ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import * as _Node from '../lighthouse-core/lib/dependency-graph/node'; -import * as _NetworkNode from '../lighthouse-core/lib/dependency-graph/network-node'; -import * as _CPUNode from '../lighthouse-core/lib/dependency-graph/cpu-node'; -import * as _Simulator from '../lighthouse-core/lib/dependency-graph/simulator/simulator'; -import * as Driver from '../lighthouse-core/gather/driver'; +import _Node = require('../lighthouse-core/lib/dependency-graph/node'); +import _NetworkNode = require('../lighthouse-core/lib/dependency-graph/network-node'); +import _CPUNode = require('../lighthouse-core/lib/dependency-graph/cpu-node'); +import _Simulator = require('../lighthouse-core/lib/dependency-graph/simulator/simulator'); +import Driver = require('../lighthouse-core/gather/driver'); declare global { module LH.Gatherer { diff --git a/typings/html-renderer.d.ts b/typings/html-renderer.d.ts new file mode 100644 index 000000000000..a137a660dbfa --- /dev/null +++ b/typings/html-renderer.d.ts @@ -0,0 +1,41 @@ +/** + * @license Copyright 2018 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +import _CategoryRenderer = require('../lighthouse-core/report/html/renderer/category-renderer.js'); +import _CriticalRequestChainRenderer = require('../lighthouse-core/report/html/renderer/crc-details-renderer.js'); +import _DetailsRenderer = require('../lighthouse-core/report/html/renderer/details-renderer.js'); +import _DOM = require('../lighthouse-core/report/html/renderer/dom.js'); +import _PerformanceCategoryRenderer = require('../lighthouse-core/report/html/renderer/performance-category-renderer.js'); +import _ReportRenderer = require('../lighthouse-core/report/html/renderer/report-renderer.js'); +import _ReportUIFeatures = require('../lighthouse-core/report/html/renderer/report-ui-features.js'); +import _Util = require('../lighthouse-core/report/html/renderer/util.js'); +import _FileNamer = require('../lighthouse-core/lib/file-namer.js'); + +declare global { + var CategoryRenderer: typeof _CategoryRenderer; + var CriticalRequestChainRenderer: typeof _CriticalRequestChainRenderer; + var DetailsRenderer: typeof _DetailsRenderer; + var DOM: typeof _DOM; + var getFilenamePrefix: typeof _FileNamer.getFilenamePrefix; + var PerformanceCategoryRenderer: typeof _PerformanceCategoryRenderer; + var ReportRenderer: typeof _ReportRenderer; + var ReportUIFeatures: typeof _ReportUIFeatures; + var Util: typeof _Util; + + interface Window { + CategoryRenderer: typeof _CategoryRenderer; + CriticalRequestChainRenderer: typeof _CriticalRequestChainRenderer; + DetailsRenderer: typeof _DetailsRenderer; + DOM: typeof _DOM; + PerformanceCategoryRenderer: typeof _PerformanceCategoryRenderer; + ReportRenderer: typeof _ReportRenderer; + ReportUIFeatures: typeof _ReportUIFeatures; + Util: typeof _Util; + } +} + +// empty export to keep file a module +export {} diff --git a/yarn.lock b/yarn.lock index 8b41c72a135c..873a24aebba7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5013,9 +5013,9 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.9.0-dev.20180323: - version "2.9.0-dev.20180323" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.0-dev.20180323.tgz#9b33b1366a2b9af88e5e4de9a8e502f1df8b5aad" +typescript@2.9.0-dev.20180510: + version "2.9.0-dev.20180510" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.0-dev.20180510.tgz#9f039785ce560e221afa418107281eecfe295e0c" uglify-js@^2.6: version "2.7.3" From 9cce1935ab6f5f6307ac20932e2ff7abbe1fb065 Mon Sep 17 00:00:00 2001 From: Brendan Kenny Date: Fri, 11 May 2018 15:52:17 -0700 Subject: [PATCH 2/5] remove closure --- .travis.yml | 1 - .../closure/closure-type-checking.js | 101 ------------------ .../closure/conformance_config.textproto | 31 ------ package.json | 4 +- yarn.lock | 58 +--------- 5 files changed, 5 insertions(+), 190 deletions(-) delete mode 100755 lighthouse-core/closure/closure-type-checking.js delete mode 100644 lighthouse-core/closure/conformance_config.textproto diff --git a/.travis.yml b/.travis.yml index 94fab7fddd78..be4e89c109d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,6 @@ script: - yarn lint - yarn unit:silentcoverage - yarn type-check - - yarn closure - yarn diff:sample-json - yarn smoke:silentcoverage - yarn test-extension diff --git a/lighthouse-core/closure/closure-type-checking.js b/lighthouse-core/closure/closure-type-checking.js deleted file mode 100755 index 1e1312f9d69e..000000000000 --- a/lighthouse-core/closure/closure-type-checking.js +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env node -/** - * @license Copyright 2016 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - */ -'use strict'; - -const closureCompiler = require('google-closure-compiler').gulp(); -const gulp = require('gulp'); -const gutil = require('gulp-util'); -const replace = require('gulp-replace'); - -// Flags to generate additional debug information. -const PRINT_AST = false; -const PRINT_CODE = !PRINT_AST && false; -const OUTPUT_FILE = PRINT_AST ? '../closure-tree.txt' : 'closure-output.js'; - -/* eslint-disable camelcase */ -gulp.task('compile-report', () => { - return gulp.src([ - // externs - 'closure/third_party/commonjs.js', - 'closure/typedefs/viewer-externs.js', - 'closure/typedefs/devtools-externs.js', - 'closure/typedefs/element-externs.js', - - 'lib/file-namer.js', - 'report/html/renderer/*.js', - '../lighthouse-viewer/app/src/viewer-ui-features.js', - ]) - - // Ignore `module.exports` and `self.ClassName = ClassName` statements. - .pipe(replace(/^\s\smodule\.exports = \w+;$/gm, ';')) - .pipe(replace(/^\s\sself\.(\w+) = \1;$/gm, ';')) - - // Remove node-specific code from file-namer so it can be included in report. - .pipe(replace(/^\s\smodule\.exports = {\w+};$/gm, ';')) - .pipe(replace('require(\'./url-shim\');', 'null;')) - .pipe(replace('(URLConstructor || URL)', 'URL')) - - .pipe(closureCompiler({ - compilation_level: 'SIMPLE', - // new_type_inf: true, - language_in: 'ECMASCRIPT6_STRICT', - language_out: 'ECMASCRIPT5_STRICT', - warning_level: 'VERBOSE', - jscomp_error: [ - 'checkTypes', - 'missingProperties', - 'accessControls', - 'const', - 'visibility', - - // This is *very* strict, so we can move back to a warning if needed. - 'reportUnknownTypes', - ], - jscomp_warning: [ - // https://github.com/google/closure-compiler/wiki/Warnings - 'checkRegExp', - 'missingReturn', - 'strictModuleDepCheck', - 'typeInvalidation', - 'undefinedNames', - - 'checkDebuggerStatement', - 'externsValidation', - 'uselessCode', - 'ambiguousFunctionDecl', - 'es3', - 'es5Strict', - 'globalThis', - 'nonStandardJsDocs', - 'suspiciousCode', - 'unknownDefines', - - // nullable/undefined checker when new_type_inf enabled. - 'newCheckTypesAllChecks', - ], - conformance_configs: 'closure/conformance_config.textproto', - - // Debug output control. - checks_only: !PRINT_CODE, - print_tree: PRINT_AST, - js_output_file: OUTPUT_FILE, - formatting: 'PRETTY_PRINT', - preserve_type_annotations: true, - })) - .on('error', err => { - gutil.log(err.message); - return process.exit(1); - }) - .pipe(gulp.dest('../')) - .on('end', () => { - gutil.log('Closure compilation successful.'); - }); -}); - -/* eslint-enable */ - -gulp.start('compile-report'); diff --git a/lighthouse-core/closure/conformance_config.textproto b/lighthouse-core/closure/conformance_config.textproto deleted file mode 100644 index 42ee1b8df755..000000000000 --- a/lighthouse-core/closure/conformance_config.textproto +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - -# This file defines the additional JS conformance tests run over lighthouse code -# when compiled with the Closure Compiler. For more, see https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework - -# Better covered by newCheckTypesAllChecks under NTI -# requirement: { -# type: CUSTOM -# java_class: 'com.google.javascript.jscomp.ConformanceRules$BanNullDeref' -# error_message: 'Dereferencing `null` or `undefined` is usually an error. See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework#bannullderef' -# } - -requirement: { - type: CUSTOM - java_class: 'com.google.javascript.jscomp.ConformanceRules$BanUnknownThis' - error_message: 'References to `this` that are typed as `unknown` are not allowed. See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework#banunknownthis' -} - -requirement: { - type: CUSTOM - java_class: 'com.google.javascript.jscomp.ConformanceRules$BanUnknownTypedClassPropsReferences' - error_message: 'References to object props that are typed as `unknown` are discouraged. See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework#banunknowntypedclasspropsreferences' -} - -requirement: { - type: CUSTOM - java_class: 'com.google.javascript.jscomp.ConformanceRules$BanUnresolvedType' - error_message: 'Forward-declared types must be included in the compilation. See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework#banunresolvedtype' -} diff --git a/package.json b/package.json index d41b78716b63..f97b65e21b15 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "debug": "node --inspect-brk ./lighthouse-cli/index.js", "start": "node ./lighthouse-cli/index.js", - "test": "yarn lint --quiet && yarn unit && yarn type-check && yarn closure && yarn diff:sample-json", + "test": "yarn lint --quiet && yarn unit && yarn type-check && yarn diff:sample-json", "test-extension": "cd lighthouse-extension && yarn test", "test-viewer": "cd lighthouse-viewer && yarn pptr-test", @@ -48,7 +48,6 @@ "coveralls": "cat unit-coverage.lcov | coveralls", "codecov": "codecov -f unit-coverage.lcov -F unit && codecov -f smoke-coverage.lcov -F smoke", - "closure": "cd lighthouse-core && node closure/closure-type-checking.js", "devtools": "bash lighthouse-core/scripts/roll-to-devtools.sh", "compile-devtools": "bash lighthouse-core/scripts/compile-against-devtools.sh", "chrome": "node lighthouse-core/scripts/manual-chrome-launcher.js", @@ -88,7 +87,6 @@ "cz-customizable": "^5.2.0", "eslint": "^4.19.1", "eslint-config-google": "^0.9.1", - "google-closure-compiler": "^20170521.0.0", "gulp": "^3.9.1", "gulp-replace": "^0.5.4", "gulp-util": "^3.0.7", diff --git a/yarn.lock b/yarn.lock index 873a24aebba7..a490f8ec0242 100644 --- a/yarn.lock +++ b/yarn.lock @@ -925,18 +925,10 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - clone@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" @@ -945,14 +937,6 @@ clone@^1.0.0, clone@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.2.tgz#260b7a99ebb1edfe247538175f783243cb19d149" -cloneable-readable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" - dependencies: - inherits "^2.0.1" - process-nextick-args "^1.0.6" - through2 "^2.0.1" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2257,14 +2241,6 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -google-closure-compiler@^20170521.0.0: - version "20170521.0.0" - resolved "https://registry.yarnpkg.com/google-closure-compiler/-/google-closure-compiler-20170521.0.0.tgz#4671caf70bd182e4c041ddb0c95f48f77695baa9" - dependencies: - chalk "^1.0.0" - vinyl "^2.0.1" - vinyl-sourcemaps-apply "^0.2.0" - got@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" @@ -2520,7 +2496,7 @@ inherits@1: version "1.0.2" resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -4023,7 +3999,7 @@ private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" -process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: +process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -4228,10 +4204,6 @@ registry-url@^3.0.3: dependencies: rc "^1.0.1" -remove-trailing-separator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz#615ebb96af559552d4bf4057c8436d486ab63cc4" - repeat-element@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" @@ -4254,10 +4226,6 @@ replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" -replace-ext@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - replacestream@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/replacestream/-/replacestream-4.0.2.tgz#0c4140707e4f0323f50de044851708cf58bc37bd" @@ -4601,7 +4569,7 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.1, source-map@^0.5.3, source-map@~0.5.1: +source-map@^0.5.3, source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -4891,7 +4859,7 @@ through2@^0.6.1: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through2@^2.0.0, through2@^2.0.1: +through2@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" dependencies: @@ -5153,12 +5121,6 @@ vinyl-fs@^0.3.0: through2 "^0.6.1" vinyl "^0.4.0" -vinyl-sourcemaps-apply@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" - dependencies: - source-map "^0.5.1" - vinyl@^0.4.0: version "0.4.6" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" @@ -5174,18 +5136,6 @@ vinyl@^0.5.0: clone-stats "^0.0.1" replace-ext "0.0.1" -vinyl@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.0.1.tgz#1c3b4931e7ac4c1efee743f3b91a74c094407bb6" - dependencies: - clone "^1.0.0" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - is-stream "^1.1.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - vscode-chrome-debug-core@^3.23.8: version "3.23.8" resolved "https://registry.yarnpkg.com/vscode-chrome-debug-core/-/vscode-chrome-debug-core-3.23.8.tgz#f0fd1582b6d7653d327171104b9c8e2eebb6bf41" From 32d85905f20d6908eb5a3b676ab43a28d29d96e7 Mon Sep 17 00:00:00 2001 From: Brendan Kenny Date: Mon, 14 May 2018 12:22:51 -0700 Subject: [PATCH 3/5] add null check --- .../report/html/renderer/report-ui-features.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lighthouse-core/report/html/renderer/report-ui-features.js b/lighthouse-core/report/html/renderer/report-ui-features.js index 1c741e80c2ff..91c38095e365 100644 --- a/lighthouse-core/report/html/renderer/report-ui-features.js +++ b/lighthouse-core/report/html/renderer/report-ui-features.js @@ -238,9 +238,13 @@ class ReportUIFeatures { scoresContainer.style.marginBottom = `${delta}px`; this.toolbar.style.transform = `translateY(${headerTransitionHeightDiff * animateScrollPercentage}px)`; - this.exportButton.parentElement.style.transform = `translateY(${headerTransitionHeightDiff * - animateScrollPercentage}px)`; this.exportButton.style.transform = `scale(${1 - 0.2 * animateScrollPercentage})`; + // fix stacking context + const exportParent = this.exportButton.parentElement; + if (exportParent) { + exportParent.style.transform = `translateY(${headerTransitionHeightDiff * + animateScrollPercentage}px)`; + } // start showing the productinfo when we are at the 50% mark of our animation const opacity = animateScrollPercentage < 0.5 ? 0 : (animateScrollPercentage - 0.5) * 2; this.productInfo.style.opacity = this.toolbarMetadata.style.opacity = opacity.toString(); From 0ac4627326d58e3818aec79b4df9c1889f927d01 Mon Sep 17 00:00:00 2001 From: Brendan Kenny Date: Thu, 17 May 2018 11:53:30 -0700 Subject: [PATCH 4/5] update tsc, last type fixes --- lighthouse-core/report/html/renderer/logger.js | 2 +- lighthouse-core/report/html/renderer/report-ui-features.js | 7 ++++--- package.json | 2 +- yarn.lock | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lighthouse-core/report/html/renderer/logger.js b/lighthouse-core/report/html/renderer/logger.js index c688f1a8f6da..88cbea90c98f 100644 --- a/lighthouse-core/report/html/renderer/logger.js +++ b/lighthouse-core/report/html/renderer/logger.js @@ -20,7 +20,7 @@ class Logger { /** * Shows a butter bar. - * @param {!string} msg The message to show. + * @param {string} msg The message to show. * @param {boolean=} autoHide True to hide the message after a duration. * Default is true. */ diff --git a/lighthouse-core/report/html/renderer/report-ui-features.js b/lighthouse-core/report/html/renderer/report-ui-features.js index 1975b2c66c53..2d3c8ee8e22e 100644 --- a/lighthouse-core/report/html/renderer/report-ui-features.js +++ b/lighthouse-core/report/html/renderer/report-ui-features.js @@ -353,9 +353,8 @@ class ReportUIFeatures { /** * Opens a new tab to the online viewer and sends the local page's JSON results * to the online viewer using postMessage. - * @param {!ReportRenderer.ReportJSON} reportJson + * @param {ReportJSON} reportJson * @param {string} viewerPath - * @suppress {reportUnknownTypes} * @protected */ static openTabAndSendJsonReport(reportJson, viewerPath) { @@ -377,7 +376,9 @@ class ReportUIFeatures { }); // The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly - const fetchTime = json.fetchTime || json.generatedTime; + // @ts-ignore - If this is a v2 LHR, use old `generatedTime`. + const fallbackFetchTime = /** @type {string} */ (json.generatedTime); + const fetchTime = json.fetchTime || fallbackFetchTime; const windowName = `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`; const popup = window.open(`${VIEWER_ORIGIN}${viewerPath}`, windowName); } diff --git a/package.json b/package.json index 4c3320875b97..c50c711ab064 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "postinstall-prepare": "^1.0.1", "puppeteer": "1.4.0", "sinon": "^2.3.5", - "typescript": "2.9.0-dev.20180510", + "typescript": "2.9.1-insiders.20180516", "vscode-chrome-debug-core": "^3.23.8", "zone.js": "^0.7.3" }, diff --git a/yarn.lock b/yarn.lock index 0a02a8e662af..60d56fe7bfd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4981,9 +4981,9 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.9.0-dev.20180510: - version "2.9.0-dev.20180510" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.0-dev.20180510.tgz#9f039785ce560e221afa418107281eecfe295e0c" +typescript@2.9.1-insiders.20180516: + version "2.9.1-insiders.20180516" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1-insiders.20180516.tgz#aab5261edb2c162c2d0c1754bb3092d4ff6efed0" uglify-js@^2.6: version "2.7.3" From f634dd97f975df0601f0935b03e202db61735f75 Mon Sep 17 00:00:00 2001 From: Brendan Kenny Date: Thu, 17 May 2018 11:59:40 -0700 Subject: [PATCH 5/5] golden LHR --- lighthouse-core/test/results/sample_v2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json index 5625ff0bd0b3..6d058e6e7919 100644 --- a/lighthouse-core/test/results/sample_v2.json +++ b/lighthouse-core/test/results/sample_v2.json @@ -1,6 +1,6 @@ { "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3358.0 Safari/537.36", - "lighthouseVersion": "3.0.0-alpha.1", + "lighthouseVersion": "3.0.0-alpha.2", "fetchTime": "2018-03-13T00:55:45.840Z", "requestedUrl": "http://localhost/dobetterweb/dbw_tester.html", "finalUrl": "http://localhost:10200/dobetterweb/dbw_tester.html",