@@ -108,7 +108,7 @@ function cleanTrace(trace) {
108108 return trace ;
109109}
110110
111- function filterPasses ( passes , audits , paths ) {
111+ function filterPasses ( passes , audits , rootPath ) {
112112 const requiredGatherers = getGatherersNeededByAudits ( audits ) ;
113113
114114 // Make sure we only have the gatherers that are needed by the audits
@@ -117,12 +117,8 @@ function filterPasses(passes, audits, paths) {
117117 const freshPass = Object . assign ( { } , pass ) ;
118118
119119 freshPass . gatherers = freshPass . gatherers . filter ( gatherer => {
120- try {
121- const GathererClass = GatherRunner . getGathererClass ( gatherer , paths ) ;
122- return requiredGatherers . has ( GathererClass . name ) ;
123- } catch ( requireError ) {
124- throw new Error ( `Unable to locate gatherer: ${ gatherer } ` ) ;
125- }
120+ const GathererClass = GatherRunner . getGathererClass ( gatherer , rootPath ) ;
121+ return requiredGatherers . has ( GathererClass . name ) ;
126122 } ) ;
127123
128124 return freshPass ;
@@ -172,77 +168,80 @@ function filterAudits(audits, auditWhitelist) {
172168 return filteredAudits ;
173169}
174170
175- function expandAudits ( audits , paths ) {
176- const rootPath = path . join ( __dirname , '../../ ' ) ;
171+ function expandAudits ( audits , rootPath ) {
172+ const Runner = require ( '../runner ' ) ;
177173
178174 return audits . map ( audit => {
179- // Check each path to see if the audit can be located. First match wins .
180- const AuditClass = paths . reduce ( ( definition , auditPath ) => {
181- // If the definition has already been found, just propagate it. Otherwise try a search
182- // on the path in this iteration of the loop.
183- if ( definition !== null ) {
184- return definition ;
185- }
175+ // Firstly see if the audit is in Lighthouse itself .
176+ const list = Runner . getAuditList ( ) ;
177+ const coreAudit = list . find ( a => a === ` ${ audit } .js` ) ;
178+
179+ // Assume it's a core audit first.
180+ let requirePath = path . resolve ( __dirname , `../audits/ ${ audit } ` ) ;
181+ let AuditClass ;
186182
187- const requirePath = auditPath . startsWith ( '/' ) ? auditPath : path . join ( rootPath , auditPath ) ;
183+ // If not, see if it can be found another way.
184+ if ( ! coreAudit ) {
185+ // Firstly try and see if the audit resolves naturally through the usual means.
188186 try {
189- return require ( `${ requirePath } /${ audit } ` ) ;
187+ require . resolve ( audit ) ;
188+
189+ // If the above works, update the path to the absolute value provided.
190+ requirePath = audit ;
190191 } catch ( requireError ) {
191- return null ;
192+ // If that fails, try and find it relative to any config path provided.
193+ if ( rootPath ) {
194+ requirePath = path . resolve ( rootPath , audit ) ;
195+ }
192196 }
193- } , null ) ;
197+ }
198+
199+ // Now try and require it in. If this fails then the audit file isn't where we expected it.
200+ try {
201+ AuditClass = require ( requirePath ) ;
202+ } catch ( requireError ) {
203+ AuditClass = null ;
204+ }
194205
195206 if ( ! AuditClass ) {
196- throw new Error ( `Unable to locate audit: ${ audit } ` ) ;
207+ throw new Error ( `Unable to locate audit: ${ audit } (tried at ${ requirePath } ) ` ) ;
197208 }
198209
199210 // Confirm that the audit appears valid.
200- const auditValidation = validateAudit ( AuditClass ) ;
201- if ( ! auditValidation . valid ) {
202- const errors = Object . keys ( auditValidation )
203- . reduce ( ( errorList , item ) => {
204- // Ignore the valid property as it's generated from the other items in the object.
205- if ( item === 'valid' ) {
206- return errorList ;
207- }
208-
209- return errorList + ( auditValidation [ item ] ? '' : `\n - ${ item } is missing` ) ;
210- } , '' ) ;
211-
212- throw new Error ( `Invalid audit class: ${ errors } ` ) ;
213- }
211+ assertValidAudit ( audit , AuditClass ) ;
214212
215213 return AuditClass ;
216214 } ) ;
217215}
218216
219- function validateAudit ( auditDefinition ) {
220- const hasAuditMethod = typeof auditDefinition . audit === 'function' ;
221- const hasMeta = typeof auditDefinition . meta === 'object' ;
222- const hasMetaName = hasMeta && typeof auditDefinition . meta . name !== 'undefined' ;
223- const hasMetaCategory = hasMeta && typeof auditDefinition . meta . category !== 'undefined' ;
224- const hasMetaDescription = hasMeta && typeof auditDefinition . meta . description !== 'undefined' ;
225- const hasMetaRequiredArtifacts = hasMeta && Array . isArray ( auditDefinition . meta . requiredArtifacts ) ;
226- const hasGenerateAuditResult = typeof auditDefinition . generateAuditResult === 'function' ;
227-
228- return {
229- 'valid' : (
230- hasAuditMethod &&
231- hasMeta &&
232- hasMetaName &&
233- hasMetaCategory &&
234- hasMetaDescription &&
235- hasMetaRequiredArtifacts &&
236- hasGenerateAuditResult
237- ) ,
238- 'audit()' : hasAuditMethod ,
239- 'meta property' : hasMeta ,
240- 'meta.name property' : hasMetaName ,
241- 'meta.category property' : hasMetaCategory ,
242- 'meta.description property' : hasMetaDescription ,
243- 'meta.requiredArtifacts array' : hasMetaRequiredArtifacts ,
244- 'generateAuditResult()' : hasGenerateAuditResult
245- } ;
217+ function assertValidAudit ( audit , auditDefinition ) {
218+ if ( typeof auditDefinition . audit !== 'function' ) {
219+ throw new Error ( `${ audit } has no audit() method.` ) ;
220+ }
221+
222+ if ( typeof auditDefinition . meta . name !== 'string' ) {
223+ throw new Error ( `${ audit } has no meta.name property, or the property is not a string.` ) ;
224+ }
225+
226+ if ( typeof auditDefinition . meta . category !== 'string' ) {
227+ throw new Error ( `${ audit } has no meta.category property, or the property is not a string.` ) ;
228+ }
229+
230+ if ( typeof auditDefinition . meta . description !== 'string' ) {
231+ throw new Error ( `${ audit } has no meta.description property, or the property is not a string.` ) ;
232+ }
233+
234+ if ( ! Array . isArray ( auditDefinition . meta . requiredArtifacts ) ) {
235+ throw new Error (
236+ `${ audit } has no meta.requiredArtifacts property, or the property is not an array.`
237+ ) ;
238+ }
239+
240+ if ( typeof auditDefinition . generateAuditResult !== 'function' ) {
241+ throw new Error (
242+ `${ audit } has no generateAuditResult() method. Did you inherit from the proper base class?`
243+ ) ;
244+ }
246245}
247246
248247function expandArtifacts ( artifacts , includeSpeedline ) {
@@ -300,58 +299,34 @@ class Config {
300299 * @constructor
301300 * @param {Object } config
302301 */
303- constructor ( configJSON , auditWhitelist ) {
302+ constructor ( configJSON , auditWhitelist , configPath ) {
304303 if ( ! configJSON ) {
305304 configJSON = defaultConfig ;
306305 }
307306
308- this . _configJSON = this . _initRequirePaths ( configJSON ) ;
307+ // Store the directory of the config path, if one was provided.
308+ this . _configDir = configPath ? path . dirname ( configPath ) : undefined ;
309309
310- this . _audits = this . json . audits ? expandAudits (
311- filterAudits ( this . json . audits , auditWhitelist ) , this . json . paths . audits
310+ this . _audits = configJSON . audits ? expandAudits (
311+ filterAudits ( configJSON . audits , auditWhitelist ) , this . _configDir
312312 ) : null ;
313313 // filterPasses expects audits to have been expanded
314- this . _passes = this . json . passes ?
315- filterPasses ( this . json . passes , this . _audits , this . json . paths . gatherers ) :
314+ this . _passes = configJSON . passes ?
315+ filterPasses ( configJSON . passes , this . _audits , this . _configDir ) :
316316 null ;
317- this . _auditResults = this . json . auditResults ? Array . from ( this . json . auditResults ) : null ;
317+ this . _auditResults = configJSON . auditResults ? Array . from ( configJSON . auditResults ) : null ;
318318 this . _artifacts = null ;
319- if ( this . json . artifacts ) {
320- this . _artifacts = expandArtifacts ( this . json . artifacts ,
319+ if ( configJSON . artifacts ) {
320+ this . _artifacts = expandArtifacts ( configJSON . artifacts ,
321321 // If time-to-interactive is present, add the speedline artifact
322- this . json . audits && this . json . audits . find ( a => a === 'time-to-interactive' ) ) ;
322+ configJSON . audits && configJSON . audits . find ( a => a === 'time-to-interactive' ) ) ;
323323 }
324- this . _aggregations = this . json . aggregations ? Array . from ( this . json . aggregations ) : null ;
325- }
326-
327- _initRequirePaths ( configJSON ) {
328- if ( typeof configJSON . paths !== 'object' ) {
329- configJSON . paths = { } ;
330- }
331-
332- if ( ! Array . isArray ( configJSON . paths . audits ) ) {
333- configJSON . paths . audits = [ ] ;
334- }
335-
336- if ( ! Array . isArray ( configJSON . paths . gatherers ) ) {
337- configJSON . paths . gatherers = [ ] ;
338- }
339-
340- // Make sure the default paths are prepended to the list
341- if ( configJSON . paths . audits . indexOf ( 'lighthouse-core/audits' ) === - 1 ) {
342- configJSON . paths . audits . unshift ( 'lighthouse-core/audits' ) ;
343- }
344-
345- if ( configJSON . paths . gatherers . indexOf ( 'lighthouse-core/gather/gatherers' ) === - 1 ) {
346- configJSON . paths . gatherers . unshift ( 'lighthouse-core/gather/gatherers' ) ;
347- }
348-
349- return configJSON ;
324+ this . _aggregations = configJSON . aggregations ? Array . from ( configJSON . aggregations ) : null ;
350325 }
351326
352- /** @type {!Object } */
353- get json ( ) {
354- return this . _configJSON ;
327+ /** @type {string } */
328+ get configDir ( ) {
329+ return this . _configDir ;
355330 }
356331
357332 /** @type {Array<!Pass> } */
0 commit comments