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

core(tsc): refactor and add type checking to config.js #5486

Merged
merged 2 commits into from
Jun 15, 2018
Merged

Conversation

brendankenny
Copy link
Member

@brendankenny brendankenny commented Jun 13, 2018

even with #5481 out of the way, this is still kind of a doozy to review, but

  • With most of the rest of lighthouse type checked, this involved significantly fewer changes than a few months ago
  • With @patrickhulce's work to unify CLI flags and the config and adding defaults for everything, a lot of simplification was already done and more was possible

Main starting change is eliminating generateNewFilteredConfig() as kind of an externally-facing API. We have a way of expressing that in the config itself now (onlyCategories, etc), so it makes sense to not expose a function for it anymore (and the extension, etc moved on to using onlyCategories, etc a while ago).

With that gone, the constructor is really the only entry point, and control flow becomes just a simple tree (ignoring deepClone and mergeOptionsOfItems):

  • make a copy of input config json
  • (optional) extend from a base config
  • clean settings and set defaults
  • clean passes and require gatherers
  • require audits
  • filter if needed
  • validate internal connections between audits, gatherers, etc

This means configs are always in one of two states, a ConfigJson -- possibly missing values and needing to extend something or be filtered -- and a Config -- all defaults set, filtered and extended. As mentioned in #5481, conveniently a Config is also a ConfigJson, enforced by the type system and some tests here.


/** @type {LH.Config['settings']} */
this._settings = configJSON.settings || {};
this.settings = settings;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason to hide these on getters (and they don't work with JSON.stringify since the getter is on the prototype)

@@ -623,90 +683,171 @@ describe('Config', () => {
});
});

describe('generateNewFilteredConfig', () => {
describe('filterConfigIfNeeded', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only changes to existing tests were

  • to use onlyCategories (etc) instead of Config.generateNewFilteredConfig directly
  • to use requireAudits/requireGatherers which now call the shorthand expansion methods instead of expandAuditShorthandAndMergeOptions directly
    caused a little code churn to maintain the same test, but the intention was for test path/result to be exactly the same

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

love the cleanup in config.js. so good


/** @type {LH.Config['settings']} */
this._settings = configJSON.settings || {};
this.settings = settings;
/** @type {LH.Config['passes']} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think these casts arent necessary any longer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, they're kind of casts, but they're not coercive, so if the lhs can't be treated as these things there will be an error. That's a nice thing to have because if future changes to config.js accidentally change the lhs type, the error will be here. If not, the class's property type will just be whatever the accidental type is, and the error will instead be deep somewhere else where the property is actually used. It's also nice for file-level documentation since these are pretty much the Config instance API.

Some of this (and some of config.d.ts) can be deleted when typescript supports @implements, though (microsoft/TypeScript#17498)

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit heavier on impl changes for a type checking PR...

but it was all bad stuff, and I'm glad it's improving 😄

}

return config;
const {defaultPassConfig} = constants;
return passes.map(pass => merge(deepClone(defaultPassConfig), pass));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I may have missed this last time, but don't we need safe deepClone here for .gatherers ala deepCloneConfigJson?

nevermind we only clone the defaults, right? 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the default is an empty gatherers array so it's fine, though I guess it could be fragile if we ever change that in the future...

if (!audits) {
return null;
}

const newAudits = audits.map(audit => {
if (typeof audit === 'string') {
return {path: audit, options: {}};
} else if (audit && typeof audit.audit === 'function') {
} else if ('implementation' in audit && typeof audit.implementation.audit === 'function') {
return audit;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the example comments for gatherer are 💯 want to add them here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea, done

static filterConfigIfNeeded(config) {
const settings = config.settings;
if (!settings.onlyCategories && !settings.onlyAudits && !settings.skipAudits) {
return config;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to add a return typedef? seems like we don't actually want to return anything

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we don't actually want to return anything

ah, yes, good call

log.warn('config', `${auditId} in 'onlyAudits' is already included by ` +
`${foundCategory} in 'onlyCategories'`);
} else {
if (auditIds.includes(auditId) && categoryIds.includes(foundCategory)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to smoosh into else if?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (!auditDefn.implementation) {
const path = auditDefn.path;
const auditDefns = expandedAudits.map(audit => {
let implementation;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this become any? or does TS infer based on the later assignments?

Copy link
Member Author

@brendankenny brendankenny Jun 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it's any here, but by the time it's out of the if/else blocks it's typeof Audit from the assignments


GathererClass = require(requirePath);
const fullPasses = passes.map(pass => {
const gathererDefns = Config.expandGathererShorthand(pass.gatherers).map(gathererDefn => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has become quite the inner loop, WDYT about extracting a method for the actual requireing part as it was before?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about extracting a method for the actual requireing part as it was before?

ok, attempted. LMKWYT

const extendedConfig = new Config(extended);

assert.equal(config.passes.length, 1, 'did not filter config');
assert.deepStrictEqual(config, extendedConfig, 'no mutations');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the message be "had mutations"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, done

@brendankenny brendankenny changed the title core(tsc): add type checking to config.js core(tsc): refactor and add type checking to config.js Jun 13, 2018
@brendankenny
Copy link
Member Author

a bit heavier on impl changes for a type checking PR...

good point. Updated the PR name :)

@brendankenny
Copy link
Member Author

🔥🔥 ❓ 🔥🔥

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🎉

@paulirish paulirish merged commit 8e76467 into master Jun 15, 2018
@paulirish paulirish deleted the tsconfigmore branch June 15, 2018 23:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants