| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const { SyncHook } = require("tapable"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | /** @typedef {import("../../declarations/WebpackOptions").Falsy} Falsy */ | 
					
						
							|  |  |  | /** @typedef {import("../../declarations/WebpackOptions").RuleSetLoaderOptions} RuleSetLoaderOptions */ | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | /** @typedef {import("../../declarations/WebpackOptions").RuleSetRule} RuleSetRule */ | 
					
						
							| 
									
										
										
										
											2025-07-10 21:30:16 +08:00
										 |  |  | /** @typedef {import("../NormalModule").LoaderItem} LoaderItem */ | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** @typedef {(Falsy | RuleSetRule)[]} RuleSetRules */ | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {(value: EffectData[keyof EffectData]) => boolean} RuleConditionFunction | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2024-06-11 01:40:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  |  * @typedef {object} RuleCondition | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  |  * @property {string | string[]} property | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  * @property {boolean} matchWhenEmpty | 
					
						
							| 
									
										
										
										
											2024-06-11 01:40:50 +08:00
										 |  |  |  * @property {RuleConditionFunction} fn | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  |  * @typedef {object} Condition | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  * @property {boolean} matchWhenEmpty | 
					
						
							| 
									
										
										
										
											2024-06-11 01:40:50 +08:00
										 |  |  |  * @property {RuleConditionFunction} fn | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  |  * @typedef {object} EffectData | 
					
						
							|  |  |  |  * @property {string=} resource | 
					
						
							|  |  |  |  * @property {string=} realResource | 
					
						
							|  |  |  |  * @property {string=} resourceQuery | 
					
						
							|  |  |  |  * @property {string=} resourceFragment | 
					
						
							|  |  |  |  * @property {string=} scheme | 
					
						
							|  |  |  |  * @property {ImportAttributes=} assertions | 
					
						
							|  |  |  |  * @property {string=} mimetype | 
					
						
							|  |  |  |  * @property {string} dependency | 
					
						
							| 
									
										
										
										
											2025-05-13 21:03:58 +08:00
										 |  |  |  * @property {Record<string, EXPECTED_ANY>=} descriptionData | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  |  * @property {string=} compiler | 
					
						
							|  |  |  |  * @property {string} issuer | 
					
						
							|  |  |  |  * @property {string} issuerLayer | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  |  * @typedef {object} CompiledRule | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  * @property {RuleCondition[]} conditions | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @property {(Effect | ((effectData: EffectData) => Effect[]))[]} effects | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  * @property {CompiledRule[]=} rules | 
					
						
							|  |  |  |  * @property {CompiledRule[]=} oneOf | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-10 21:30:16 +08:00
										 |  |  | /** @typedef {"use" | "use-pre" | "use-post"} EffectUseType */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-07-10 21:30:16 +08:00
										 |  |  |  * @typedef {object} EffectUse | 
					
						
							|  |  |  |  * @property {EffectUseType} type | 
					
						
							|  |  |  |  * @property {{ loader: string, options?: string | null | Record<string, EXPECTED_ANY>, ident?: string }} value | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @typedef {object} EffectBasic | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  * @property {string} type | 
					
						
							| 
									
										
										
										
											2025-07-10 21:30:16 +08:00
										 |  |  |  * @property {EXPECTED_ANY} value | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-10 21:30:16 +08:00
										 |  |  | /** @typedef {EffectUse | EffectBasic} Effect */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | /** @typedef {Map<string, RuleSetLoaderOptions>} References */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2024-06-11 21:09:50 +08:00
										 |  |  |  * @typedef {object} RuleSet | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  |  * @property {References} references map of references in the rule set (may grow over time) | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  |  * @property {(effectData: EffectData) => Effect[]} exec execute the rule set | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @template T | 
					
						
							|  |  |  |  * @template {T[keyof T]} V | 
					
						
							|  |  |  |  * @typedef {({ [P in keyof Required<T>]: Required<T>[P] extends V ? P : never })[keyof T]} KeysOfTypes | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-12 09:56:14 +08:00
										 |  |  | /** @typedef {{ apply: (ruleSetCompiler: RuleSetCompiler) => void }} RuleSetPlugin */ | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | class RuleSetCompiler { | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {RuleSetPlugin[]} plugins plugins | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	constructor(plugins) { | 
					
						
							|  |  |  | 		this.hooks = Object.freeze({ | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 			/** @type {SyncHook<[string, RuleSetRule, Set<string>, CompiledRule, References]>} */ | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			rule: new SyncHook([ | 
					
						
							|  |  |  | 				"path", | 
					
						
							|  |  |  | 				"rule", | 
					
						
							|  |  |  | 				"unhandledProperties", | 
					
						
							|  |  |  | 				"compiledRule", | 
					
						
							|  |  |  | 				"references" | 
					
						
							|  |  |  | 			]) | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		if (plugins) { | 
					
						
							|  |  |  | 			for (const plugin of plugins) { | 
					
						
							|  |  |  | 				plugin.apply(this); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 	 * @param {RuleSetRules} ruleSet raw user provided rules | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	 * @returns {RuleSet} compiled RuleSet | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	compile(ruleSet) { | 
					
						
							|  |  |  | 		const refs = new Map(); | 
					
						
							|  |  |  | 		const rules = this.compileRules("ruleSet", ruleSet, refs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 		 * @param {EffectData} data data passed in | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 		 * @param {CompiledRule} rule the compiled rule | 
					
						
							|  |  |  | 		 * @param {Effect[]} effects an array where effects are pushed to | 
					
						
							|  |  |  | 		 * @returns {boolean} true, if the rule has matched | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		const execRule = (data, rule, effects) => { | 
					
						
							|  |  |  | 			for (const condition of rule.conditions) { | 
					
						
							|  |  |  | 				const p = condition.property; | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  | 				if (Array.isArray(p)) { | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  | 					/** @type {EffectData | EffectData[keyof EffectData] | undefined} */ | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  | 					let current = data; | 
					
						
							|  |  |  | 					for (const subProperty of p) { | 
					
						
							| 
									
										
										
										
											2020-07-17 21:41:30 +08:00
										 |  |  | 						if ( | 
					
						
							|  |  |  | 							current && | 
					
						
							|  |  |  | 							typeof current === "object" && | 
					
						
							|  |  |  | 							Object.prototype.hasOwnProperty.call(current, subProperty) | 
					
						
							|  |  |  | 						) { | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  | 							current = current[/** @type {keyof EffectData} */ (subProperty)]; | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  | 						} else { | 
					
						
							|  |  |  | 							current = undefined; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if (current !== undefined) { | 
					
						
							| 
									
										
										
										
											2024-10-24 04:30:31 +08:00
										 |  |  | 						if (!condition.fn(current)) return false; | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  | 						continue; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} else if (p in data) { | 
					
						
							| 
									
										
										
										
											2025-05-01 22:36:51 +08:00
										 |  |  | 					const value = data[/** @type {keyof EffectData} */ (p)]; | 
					
						
							| 
									
										
										
										
											2020-12-17 18:00:24 +08:00
										 |  |  | 					if (value !== undefined) { | 
					
						
							|  |  |  | 						if (!condition.fn(value)) return false; | 
					
						
							|  |  |  | 						continue; | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-07-17 16:27:48 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				if (!condition.matchWhenEmpty) { | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 					return false; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for (const effect of rule.effects) { | 
					
						
							|  |  |  | 				if (typeof effect === "function") { | 
					
						
							|  |  |  | 					const returnedEffects = effect(data); | 
					
						
							|  |  |  | 					for (const effect of returnedEffects) { | 
					
						
							|  |  |  | 						effects.push(effect); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					effects.push(effect); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (rule.rules) { | 
					
						
							|  |  |  | 				for (const childRule of rule.rules) { | 
					
						
							|  |  |  | 					execRule(data, childRule, effects); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (rule.oneOf) { | 
					
						
							|  |  |  | 				for (const childRule of rule.oneOf) { | 
					
						
							|  |  |  | 					if (execRule(data, childRule, effects)) { | 
					
						
							|  |  |  | 						break; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return { | 
					
						
							|  |  |  | 			references: refs, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			exec: (data) => { | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 				/** @type {Effect[]} */ | 
					
						
							|  |  |  | 				const effects = []; | 
					
						
							|  |  |  | 				for (const rule of rules) { | 
					
						
							|  |  |  | 					execRule(data, rule, effects); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return effects; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} path current path | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 	 * @param {RuleSetRules} rules the raw rules provided by user | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 	 * @param {References} refs references | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	 * @returns {CompiledRule[]} rules | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	compileRules(path, rules, refs) { | 
					
						
							| 
									
										
										
										
											2023-06-07 09:07:31 +08:00
										 |  |  | 		return rules | 
					
						
							|  |  |  | 			.filter(Boolean) | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 			.map((rule, i) => | 
					
						
							|  |  |  | 				this.compileRule( | 
					
						
							|  |  |  | 					`${path}[${i}]`, | 
					
						
							|  |  |  | 					/** @type {RuleSetRule} */ (rule), | 
					
						
							|  |  |  | 					refs | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} path current path | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 	 * @param {RuleSetRule} rule the raw rule provided by user | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 	 * @param {References} refs references | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	 * @returns {CompiledRule} normalized and compiled rule for processing | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	compileRule(path, rule, refs) { | 
					
						
							| 
									
										
										
										
											2025-07-03 17:06:45 +08:00
										 |  |  | 		/** @type {Set<string>} */ | 
					
						
							| 
									
										
										
										
											2019-07-12 18:19:51 +08:00
										 |  |  | 		const unhandledProperties = new Set( | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 			Object.keys(rule).filter( | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				(key) => rule[/** @type {keyof RuleSetRule} */ (key)] !== undefined | 
					
						
							| 
									
										
										
										
											2024-08-06 11:08:48 +08:00
										 |  |  | 			) | 
					
						
							| 
									
										
										
										
											2019-07-12 18:19:51 +08:00
										 |  |  | 		); | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/** @type {CompiledRule} */ | 
					
						
							|  |  |  | 		const compiledRule = { | 
					
						
							|  |  |  | 			conditions: [], | 
					
						
							|  |  |  | 			effects: [], | 
					
						
							|  |  |  | 			rules: undefined, | 
					
						
							|  |  |  | 			oneOf: undefined | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.hooks.rule.call(path, rule, unhandledProperties, compiledRule, refs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (unhandledProperties.has("rules")) { | 
					
						
							|  |  |  | 			unhandledProperties.delete("rules"); | 
					
						
							|  |  |  | 			const rules = rule.rules; | 
					
						
							| 
									
										
										
										
											2025-07-02 20:10:54 +08:00
										 |  |  | 			if (!Array.isArray(rules)) { | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 				throw this.error(path, rules, "Rule.rules must be an array of rules"); | 
					
						
							| 
									
										
										
										
											2025-07-02 20:10:54 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			compiledRule.rules = this.compileRules(`${path}.rules`, rules, refs); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (unhandledProperties.has("oneOf")) { | 
					
						
							|  |  |  | 			unhandledProperties.delete("oneOf"); | 
					
						
							|  |  |  | 			const oneOf = rule.oneOf; | 
					
						
							| 
									
										
										
										
											2025-07-02 20:10:54 +08:00
										 |  |  | 			if (!Array.isArray(oneOf)) { | 
					
						
							| 
									
										
										
										
											2019-05-20 15:20:18 +08:00
										 |  |  | 				throw this.error(path, oneOf, "Rule.oneOf must be an array of rules"); | 
					
						
							| 
									
										
										
										
											2025-07-02 20:10:54 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			compiledRule.oneOf = this.compileRules(`${path}.oneOf`, oneOf, refs); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (unhandledProperties.size > 0) { | 
					
						
							|  |  |  | 			throw this.error( | 
					
						
							|  |  |  | 				path, | 
					
						
							|  |  |  | 				rule, | 
					
						
							| 
									
										
										
										
											2025-07-03 17:06:45 +08:00
										 |  |  | 				`Properties ${[...unhandledProperties].join(", ")} are unknown` | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return compiledRule; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} path current path | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 	 * @param {RuleSetLoaderOptions} condition user provided condition value | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	 * @returns {Condition} compiled condition | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	compileCondition(path, condition) { | 
					
						
							| 
									
										
										
										
											2020-12-22 21:51:09 +08:00
										 |  |  | 		if (condition === "") { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				matchWhenEmpty: true, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				fn: (str) => str === "" | 
					
						
							| 
									
										
										
										
											2020-12-22 21:51:09 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 		if (!condition) { | 
					
						
							|  |  |  | 			throw this.error( | 
					
						
							|  |  |  | 				path, | 
					
						
							|  |  |  | 				condition, | 
					
						
							|  |  |  | 				"Expected condition but got falsy value" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (typeof condition === "string") { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				matchWhenEmpty: condition.length === 0, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				fn: (str) => typeof str === "string" && str.startsWith(condition) | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (typeof condition === "function") { | 
					
						
							|  |  |  | 			try { | 
					
						
							|  |  |  | 				return { | 
					
						
							|  |  |  | 					matchWhenEmpty: condition(""), | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 					fn: /** @type {RuleConditionFunction} */ (condition) | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 				}; | 
					
						
							| 
									
										
										
										
											2024-07-31 15:37:05 +08:00
										 |  |  | 			} catch (_err) { | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 				throw this.error( | 
					
						
							|  |  |  | 					path, | 
					
						
							|  |  |  | 					condition, | 
					
						
							|  |  |  | 					"Evaluation of condition function threw error" | 
					
						
							|  |  |  | 				); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (condition instanceof RegExp) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				matchWhenEmpty: condition.test(""), | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 				fn: (v) => typeof v === "string" && condition.test(v) | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (Array.isArray(condition)) { | 
					
						
							|  |  |  | 			const items = condition.map((c, i) => | 
					
						
							|  |  |  | 				this.compileCondition(`${path}[${i}]`, c) | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 			return this.combineConditionsOr(items); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (typeof condition !== "object") { | 
					
						
							|  |  |  | 			throw this.error( | 
					
						
							|  |  |  | 				path, | 
					
						
							|  |  |  | 				condition, | 
					
						
							| 
									
										
										
										
											2020-03-13 00:51:26 +08:00
										 |  |  | 				`Unexpected ${typeof condition} when condition was expected` | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const conditions = []; | 
					
						
							|  |  |  | 		for (const key of Object.keys(condition)) { | 
					
						
							|  |  |  | 			const value = condition[key]; | 
					
						
							|  |  |  | 			switch (key) { | 
					
						
							|  |  |  | 				case "or": | 
					
						
							|  |  |  | 					if (value) { | 
					
						
							|  |  |  | 						if (!Array.isArray(value)) { | 
					
						
							|  |  |  | 							throw this.error( | 
					
						
							|  |  |  | 								`${path}.or`, | 
					
						
							| 
									
										
										
										
											2023-09-19 11:48:22 +08:00
										 |  |  | 								condition.or, | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 								"Expected array of conditions" | 
					
						
							|  |  |  | 							); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						conditions.push(this.compileCondition(`${path}.or`, value)); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case "and": | 
					
						
							|  |  |  | 					if (value) { | 
					
						
							|  |  |  | 						if (!Array.isArray(value)) { | 
					
						
							|  |  |  | 							throw this.error( | 
					
						
							|  |  |  | 								`${path}.and`, | 
					
						
							|  |  |  | 								condition.and, | 
					
						
							|  |  |  | 								"Expected array of conditions" | 
					
						
							|  |  |  | 							); | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						let i = 0; | 
					
						
							|  |  |  | 						for (const item of value) { | 
					
						
							|  |  |  | 							conditions.push(this.compileCondition(`${path}.and[${i}]`, item)); | 
					
						
							|  |  |  | 							i++; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				case "not": | 
					
						
							|  |  |  | 					if (value) { | 
					
						
							|  |  |  | 						const matcher = this.compileCondition(`${path}.not`, value); | 
					
						
							|  |  |  | 						const fn = matcher.fn; | 
					
						
							|  |  |  | 						conditions.push({ | 
					
						
							|  |  |  | 							matchWhenEmpty: !matcher.matchWhenEmpty, | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 							fn: /** @type {RuleConditionFunction} */ ((v) => !fn(v)) | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 						}); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					throw this.error( | 
					
						
							|  |  |  | 						`${path}.${key}`, | 
					
						
							|  |  |  | 						condition[key], | 
					
						
							| 
									
										
										
										
											2020-03-13 00:51:26 +08:00
										 |  |  | 						`Unexpected property ${key} in condition` | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 					); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (conditions.length === 0) { | 
					
						
							|  |  |  | 			throw this.error( | 
					
						
							|  |  |  | 				path, | 
					
						
							|  |  |  | 				condition, | 
					
						
							|  |  |  | 				"Expected condition, but got empty thing" | 
					
						
							|  |  |  | 			); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return this.combineConditionsAnd(conditions); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Condition[]} conditions some conditions | 
					
						
							|  |  |  | 	 * @returns {Condition} merged condition | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	combineConditionsOr(conditions) { | 
					
						
							|  |  |  | 		if (conditions.length === 0) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				matchWhenEmpty: false, | 
					
						
							|  |  |  | 				fn: () => false | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} else if (conditions.length === 1) { | 
					
						
							|  |  |  | 			return conditions[0]; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-31 04:21:27 +08:00
										 |  |  | 		return { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			matchWhenEmpty: conditions.some((c) => c.matchWhenEmpty), | 
					
						
							|  |  |  | 			fn: (v) => conditions.some((c) => c.fn(v)) | 
					
						
							| 
									
										
										
										
											2024-07-31 04:21:27 +08:00
										 |  |  | 		}; | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Condition[]} conditions some conditions | 
					
						
							|  |  |  | 	 * @returns {Condition} merged condition | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	combineConditionsAnd(conditions) { | 
					
						
							|  |  |  | 		if (conditions.length === 0) { | 
					
						
							|  |  |  | 			return { | 
					
						
							|  |  |  | 				matchWhenEmpty: false, | 
					
						
							|  |  |  | 				fn: () => false | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 		} else if (conditions.length === 1) { | 
					
						
							|  |  |  | 			return conditions[0]; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-31 04:21:27 +08:00
										 |  |  | 		return { | 
					
						
							| 
									
										
										
										
											2025-07-17 00:13:14 +08:00
										 |  |  | 			matchWhenEmpty: conditions.every((c) => c.matchWhenEmpty), | 
					
						
							|  |  |  | 			fn: (v) => conditions.every((c) => c.fn(v)) | 
					
						
							| 
									
										
										
										
											2024-07-31 04:21:27 +08:00
										 |  |  | 		}; | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} path current path | 
					
						
							| 
									
										
										
										
											2025-03-27 08:07:25 +08:00
										 |  |  | 	 * @param {EXPECTED_ANY} value value at the error location | 
					
						
							| 
									
										
										
										
											2020-03-10 09:59:46 +08:00
										 |  |  | 	 * @param {string} message message explaining the problem | 
					
						
							| 
									
										
										
										
											2019-05-16 17:31:41 +08:00
										 |  |  | 	 * @returns {Error} an error object | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	error(path, value, message) { | 
					
						
							|  |  |  | 		return new Error( | 
					
						
							|  |  |  | 			`Compiling RuleSet failed: ${message} (at ${path}: ${value})` | 
					
						
							|  |  |  | 		); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = RuleSetCompiler; |