Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Closure type checking for report v2 #2043

Merged
merged 3 commits into from
Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ before_script:
script:
- npm run lint
- npm run unit
- npm run closure
- npm run smoke
- npm run smokehouse
after_success:
Expand Down
121 changes: 65 additions & 56 deletions lighthouse-core/closure/closure-type-checking.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,67 +22,76 @@ 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('js-compile', function() {
gulp.task('compile-report', () => {
return gulp.src([
'closure/typedefs/*.js',
'closure/third_party/*.js',
'audits/**/*.js',
'lib/event-helpers.js',
'lib/icons.js',
'lib/styles-helpers.js',
'lib/url-shim.js',
'aggregator/**/*.js'
// externs
'closure/third_party/commonjs.js',

'report/v2/renderer/*.js',
])
// Hack to remove `require`s that Closure currently can't resolve.
.pipe(replace('require(\'../lib/web-inspector\').Color.parse;',
'WebInspector.Color.parse;'))
.pipe(replace('require(\'../lib/traces/tracing-processor\');', '/** @type {?} */ (null);'))
.pipe(replace('require(\'../lib/traces/devtools-timeline-model\');',
'DevtoolsTimelineModel'))
.pipe(replace('require(\'speedline\');', 'function(arg) {};'))
.pipe(replace(/require\('(\.\.\/)*report\/formatter'\);/g, '{};'))

// Replace any non-local import (e.g. not starting with .) with a dummy type. These are likely
// the built-in Node modules. But not always, so TODO(samthor): Fix this.
.pipe(replace(/require\(\'[^\.].*?\'\)/g, '/** @type {*} */ ({})'))
// Ignore `module.exports` and `self.ClassName = ClassName` statements.
.pipe(replace(/^\s\smodule\.exports = \w+;$/gm, ';'))
.pipe(replace(/^\s\sself\.(\w+) = \1;$/gm, ';'))

.pipe(closureCompiler({
compilation_level: 'SIMPLE',
// new_type_inf: true,
language_in: 'ECMASCRIPT6_STRICT',
language_out: 'ECMASCRIPT5_STRICT',
warning_level: process.env.CI ? 'QUIET' : 'VERBOSE',
jscomp_error: [
'checkTypes',
],
jscomp_warning: [
// https://github.com/google/closure-compiler/wiki/Warnings
'accessControls',
'checkRegExp',
'const',
'reportUnknownTypes',
'missingProperties',
'missingReturn',
'strictModuleDepCheck',
'typeInvalidation',
'undefinedNames',
'visibility',

.pipe(closureCompiler({
compilation_level: 'SIMPLE',
process_common_js_modules: true,
new_type_inf: true,
checks_only: true,
language_in: 'ECMASCRIPT6_STRICT',
language_out: 'ECMASCRIPT5_STRICT',
warning_level: process.env.CI ? 'QUIET' : 'VERBOSE',
jscomp_error: [
'checkTypes',
'conformanceViolations'
],
jscomp_warning: [
// https://github.com/google/closure-compiler/wiki/Warnings
'accessControls',
'checkRegExp',
'const',
// 'reportUnknownTypes',
'missingProperties',
'missingReturn',
'newCheckTypes',
'strictModuleDepCheck',
'typeInvalidation',
'undefinedNames',
'visibility'
],
conformance_configs: 'closure/conformance_config.textproto'
}))
.on('error', error => {
gutil.log('Closure compilation failed. Check `closure-error.log` for details.');
require('fs').writeFileSync('closure-error.log', error);
})
.on('end', () => {
gutil.log('Closure compilation successful.');
});
'checkDebuggerStatement',
'externsValidation',
'uselessCode',
'ambiguousFunctionDecl',
'checkTypes',
'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,
}))
.pipe(gulp.dest('../'))
.on('end', () => {
gutil.log('Closure compilation successful.');
});
});

/* eslint-enable */

gulp.start('js-compile');
gulp.start('compile-report');
18 changes: 9 additions & 9 deletions lighthouse-core/closure/conformance_config.textproto
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
# 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$BanNullDeref'
error_message: 'Dereferencing `null` or `undefined` is usually an error. See https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework#bannullderef'
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'
}

# TODO(bckenny): fix unknown this error(s)
# 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'
Expand Down
25 changes: 25 additions & 0 deletions lighthouse-core/closure/third_party/commonjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2017 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.
*/

/**
* @externs
*/

/** @const */
const module = {};

/** @type {*} */
module.exports;
41 changes: 16 additions & 25 deletions lighthouse-core/report/v2/renderer/details-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,22 @@ class DetailsRenderer {
* @param {!DOM} dom
*/
constructor(dom) {
/** @private {!DOM} */
this._dom = dom;
}

/**
* @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details
* @param {!DetailsRenderer.DetailsJSON} details
* @return {!Element}
*/
render(details) {
switch (details.type) {
case 'text':
return this._renderText(details);
case 'block':
return this._renderBlock(details);
case 'cards':
return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));
case 'list':
return this._renderList(details);
return this._renderList(/** @type {!DetailsRenderer.ListDetailsJSON} */ (details));
default:
throw new Error(`Unknown type: ${details.type}`);
}
Expand All @@ -55,20 +54,7 @@ class DetailsRenderer {
}

/**
* @param {!DetailsRenderer.DetailsJSON} block
* @return {!Element}
*/
_renderBlock(block) {
const element = this._dom.createElement('div', 'lh-block');
const items = block.items || [];
for (const item of items) {
element.appendChild(this.render(item));
}
return element;
}

/**
* @param {!DetailsRenderer.DetailsJSON} list
* @param {!DetailsRenderer.ListDetailsJSON} list
* @return {!Element}
*/
_renderList(list) {
Expand All @@ -80,8 +66,7 @@ class DetailsRenderer {
}

const itemsElem = this._dom.createElement('div', 'lh-list__items');
const items = list.items || [];
for (const item of items) {
for (const item of list.items) {
itemsElem.appendChild(this.render(item));
}
element.appendChild(itemsElem);
Expand Down Expand Up @@ -128,17 +113,23 @@ if (typeof module !== 'undefined' && module.exports) {
/**
* @typedef {{
* type: string,
* text: (string|undefined),
* header: (!DetailsRenderer.DetailsJSON|undefined),
* items: (!Array<!DetailsRenderer.DetailsJSON>|undefined)
* text: (string|undefined)
* }}
*/
DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions

/**
* @typedef {{
* type: string,
* header: ({text: string}|undefined),
* items: !Array<{type: string, text: (string|undefined)}>
* }}
*/
DetailsRenderer.ListDetailsJSON; // eslint-disable-line no-unused-expressions

/** @typedef {{
* type: string,
* text: string,
* header: !DetailsRenderer.DetailsJSON,
* header: ({text: string}|undefined),
* items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>
* }}
*/
Expand Down
24 changes: 19 additions & 5 deletions lighthouse-core/report/v2/renderer/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DOM {
* @param {!Document} document
*/
constructor(document) {
/** @private {!Document} */
this._document = document;
}

Expand All @@ -33,9 +34,7 @@ class DOM {
* set the attribute on the node.
* @return {!Element}
*/
createElement(name, className, attrs) {
// TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands
attrs = attrs || {};
createElement(name, className, attrs = {}) {
const element = this._document.createElement(name);
if (className) {
element.className = className;
Expand All @@ -56,7 +55,7 @@ class DOM {
* @throws {Error}
*/
cloneTemplate(selector, context) {
const template = context.querySelector(selector);
const template = /** @type {?HTMLTemplateElement} */ (context.querySelector(selector));
if (!template) {
throw new Error(`Template not found: template${selector}`);
}
Expand All @@ -80,7 +79,7 @@ class DOM {

// Append link if there are any.
if (linkText && linkHref) {
const a = this.createElement('a');
const a = /** @type {!HTMLAnchorElement} */ (this.createElement('a'));
a.rel = 'noopener';
a.target = '_blank';
a.textContent = linkText;
Expand All @@ -98,6 +97,21 @@ class DOM {
document() {
return this._document;
}

/**
* Guaranteed context.querySelector. Always returns an element or throws if
* nothing matches query.
* @param {string} query
* @param {!DocumentFragment|!Element} context
* @return {!Element}
*/
find(context, query) {
const result = context.querySelector(query);
if (result === null) {
throw new Error(`query ${query} not found`);
}
return result;
}
}

if (typeof module !== 'undefined' && module.exports) {
Expand Down
Loading