| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-05-20 14:41:44 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  | import {error} from '../utils/console'; | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  | import {convertConditionToFunction} from './condition_evaluator'; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | import {PullApproveGroupConfig} from './parse-yaml'; | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  | import {PullApproveGroupStateDependencyError} from './pullapprove_arrays'; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** A condition for a group. */ | 
					
						
							|  |  |  | interface GroupCondition { | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   expression: string; | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  |   checkFn: (files: string[], groups: PullApproveGroup[]) => boolean; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |   matchedFiles: Set<string>; | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |   unverifiable: boolean; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Result of testing files against the group. */ | 
					
						
							|  |  |  | export interface PullApproveGroupResult { | 
					
						
							|  |  |  |   groupName: string; | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   matchedConditions: GroupCondition[]; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |   matchedCount: number; | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   unmatchedConditions: GroupCondition[]; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |   unmatchedCount: number; | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |   unverifiableConditions: GroupCondition[]; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  | // Regular expression that matches conditions for the global approval.
 | 
					
						
							|  |  |  | const GLOBAL_APPROVAL_CONDITION_REGEX = /^"global-(docs-)?approvers" not in groups.approved$/; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  | // Name of the PullApprove group that serves as fallback. This group should never capture
 | 
					
						
							|  |  |  | // any conditions as it would always match specified files. This is not desired as we want
 | 
					
						
							|  |  |  | // to figure out as part of this tool, whether there actually are unmatched files.
 | 
					
						
							|  |  |  | const FALLBACK_GROUP_NAME = 'fallback'; | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** A PullApprove group to be able to test files against. */ | 
					
						
							|  |  |  | export class PullApproveGroup { | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   /** List of conditions for the group. */ | 
					
						
							|  |  |  |   conditions: GroupCondition[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |       public groupName: string, config: PullApproveGroupConfig, | 
					
						
							|  |  |  |       readonly precedingGroups: PullApproveGroup[] = []) { | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |     this._captureConditions(config); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   private _captureConditions(config: PullApproveGroupConfig) { | 
					
						
							|  |  |  |     if (config.conditions && this.groupName !== FALLBACK_GROUP_NAME) { | 
					
						
							|  |  |  |       return config.conditions.forEach(condition => { | 
					
						
							|  |  |  |         const expression = condition.trim(); | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |         if (expression.match(GLOBAL_APPROVAL_CONDITION_REGEX)) { | 
					
						
							| 
									
										
										
										
											2020-03-30 08:44:30 -07:00
										 |  |  |           // Currently a noop as we don't take any action for global approval conditions.
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           this.conditions.push({ | 
					
						
							|  |  |  |             expression, | 
					
						
							|  |  |  |             checkFn: convertConditionToFunction(expression), | 
					
						
							|  |  |  |             matchedFiles: new Set(), | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |             unverifiable: false, | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |           }); | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							| 
									
										
										
										
											2020-05-20 14:41:44 -07:00
										 |  |  |           error(`Could not parse condition in group: ${this.groupName}`); | 
					
						
							|  |  |  |           error(` - ${expression}`); | 
					
						
							|  |  |  |           error(`Error:`); | 
					
						
							|  |  |  |           error(e.message); | 
					
						
							|  |  |  |           error(e.stack); | 
					
						
							| 
									
										
										
										
											2020-03-30 08:44:30 -07:00
										 |  |  |           process.exit(1); | 
					
						
							| 
									
										
										
										
											2020-03-27 18:14:52 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Tests a provided file path to determine if it would be considered matched by | 
					
						
							|  |  |  |    * the pull approve group's conditions. | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   testFile(filePath: string): boolean { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |     return this.conditions.every((condition) => { | 
					
						
							|  |  |  |       const {matchedFiles, checkFn, expression} = condition; | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |       try { | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  |         const matchesFile = checkFn([filePath], this.precedingGroups); | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |         if (matchesFile) { | 
					
						
							|  |  |  |           matchedFiles.add(filePath); | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |         return matchesFile; | 
					
						
							|  |  |  |       } catch (e) { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |         // In the case of a condition that depends on the state of groups we want to
 | 
					
						
							|  |  |  |         // ignore that the verification can't accurately evaluate the condition and then
 | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  |         // continue processing. Other types of errors fail the verification, as conditions
 | 
					
						
							|  |  |  |         // should otherwise be able to execute without throwing.
 | 
					
						
							|  |  |  |         if (e instanceof PullApproveGroupStateDependencyError) { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |           condition.unverifiable = true; | 
					
						
							|  |  |  |           // Return true so that `this.conditions.every` can continue evaluating.
 | 
					
						
							|  |  |  |           return true; | 
					
						
							| 
									
										
										
										
											2020-07-20 11:53:26 -07:00
										 |  |  |         } else { | 
					
						
							|  |  |  |           const errMessage = `Condition could not be evaluated: \n\n` + | 
					
						
							|  |  |  |               `From the [${this.groupName}] group:\n` + | 
					
						
							|  |  |  |               ` - ${expression}` + | 
					
						
							|  |  |  |               `\n\n${e.message} ${e.stack}\n\n`; | 
					
						
							|  |  |  |           error(errMessage); | 
					
						
							|  |  |  |           process.exit(1); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |   /** Retrieve the results for the Group, all matched and unmatched conditions. */ | 
					
						
							|  |  |  |   getResults(): PullApproveGroupResult { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |     const matchedConditions = this.conditions.filter(c => c.matchedFiles.size > 0); | 
					
						
							|  |  |  |     const unmatchedConditions = | 
					
						
							|  |  |  |         this.conditions.filter(c => c.matchedFiles.size === 0 && !c.unverifiable); | 
					
						
							|  |  |  |     const unverifiableConditions = this.conditions.filter(c => c.unverifiable); | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |     return { | 
					
						
							|  |  |  |       matchedConditions, | 
					
						
							|  |  |  |       matchedCount: matchedConditions.length, | 
					
						
							|  |  |  |       unmatchedConditions, | 
					
						
							|  |  |  |       unmatchedCount: unmatchedConditions.length, | 
					
						
							| 
									
										
										
										
											2020-07-23 15:53:46 -07:00
										 |  |  |       unverifiableConditions, | 
					
						
							| 
									
										
										
										
											2020-04-16 20:12:25 +02:00
										 |  |  |       groupName: this.groupName, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-03-20 06:59:35 -07:00
										 |  |  | } |