You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							567 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							567 lines
						
					
					
						
							12 KiB
						
					
					
				/* | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php | 
						|
	Author Tobias Koppers @sokra | 
						|
*/ | 
						|
/* | 
						|
<rules>: <rule> | 
						|
<rules>: [<rule>] | 
						|
<rule>: { | 
						|
	resource: { | 
						|
		test: <condition>, | 
						|
		include: <condition>, | 
						|
		exclude: <condition>, | 
						|
	}, | 
						|
	resource: <condition>, -> resource.test | 
						|
	test: <condition>, -> resource.test | 
						|
	include: <condition>, -> resource.include | 
						|
	exclude: <condition>, -> resource.exclude | 
						|
	resourceQuery: <condition>, | 
						|
	compiler: <condition>, | 
						|
	issuer: <condition>, | 
						|
	use: "loader", -> use[0].loader | 
						|
	loader: <>, -> use[0].loader | 
						|
	loaders: <>, -> use | 
						|
	options: {}, -> use[0].options, | 
						|
	query: {}, -> options | 
						|
	parser: {}, | 
						|
	use: [ | 
						|
		"loader" -> use[x].loader | 
						|
	], | 
						|
	use: [ | 
						|
		{ | 
						|
			loader: "loader", | 
						|
			options: {} | 
						|
		} | 
						|
	], | 
						|
	rules: [ | 
						|
		<rule> | 
						|
	], | 
						|
	oneOf: [ | 
						|
		<rule> | 
						|
	] | 
						|
} | 
						|
 | 
						|
<condition>: /regExp/ | 
						|
<condition>: function(arg) {} | 
						|
<condition>: "starting" | 
						|
<condition>: [<condition>] // or | 
						|
<condition>: { and: [<condition>] } | 
						|
<condition>: { or: [<condition>] } | 
						|
<condition>: { not: [<condition>] } | 
						|
<condition>: { test: <condition>, include: <condition>, exclude: <condition> } | 
						|
 | 
						|
 | 
						|
normalized: | 
						|
 | 
						|
{ | 
						|
	resource: function(), | 
						|
	resourceQuery: function(), | 
						|
	compiler: function(), | 
						|
	issuer: function(), | 
						|
	use: [ | 
						|
		{ | 
						|
			loader: string, | 
						|
			options: string, | 
						|
			<any>: <any> | 
						|
		} | 
						|
	], | 
						|
	rules: [<rule>], | 
						|
	oneOf: [<rule>], | 
						|
	<any>: <any>, | 
						|
} | 
						|
 | 
						|
*/ | 
						|
 | 
						|
"use strict"; | 
						|
 | 
						|
const notMatcher = matcher => { | 
						|
	return str => { | 
						|
		return !matcher(str); | 
						|
	}; | 
						|
}; | 
						|
 | 
						|
const orMatcher = items => { | 
						|
	return str => { | 
						|
		for (let i = 0; i < items.length; i++) { | 
						|
			if (items[i](str)) return true; | 
						|
		} | 
						|
		return false; | 
						|
	}; | 
						|
}; | 
						|
 | 
						|
const andMatcher = items => { | 
						|
	return str => { | 
						|
		for (let i = 0; i < items.length; i++) { | 
						|
			if (!items[i](str)) return false; | 
						|
		} | 
						|
		return true; | 
						|
	}; | 
						|
}; | 
						|
 | 
						|
module.exports = class RuleSet { | 
						|
	constructor(rules) { | 
						|
		this.references = Object.create(null); | 
						|
		this.rules = RuleSet.normalizeRules(rules, this.references, "ref-"); | 
						|
	} | 
						|
 | 
						|
	static normalizeRules(rules, refs, ident) { | 
						|
		if (Array.isArray(rules)) { | 
						|
			return rules.map((rule, idx) => { | 
						|
				return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`); | 
						|
			}); | 
						|
		} else if (rules) { | 
						|
			return [RuleSet.normalizeRule(rules, refs, ident)]; | 
						|
		} else { | 
						|
			return []; | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	static normalizeRule(rule, refs, ident) { | 
						|
		if (typeof rule === "string") { | 
						|
			return { | 
						|
				use: [ | 
						|
					{ | 
						|
						loader: rule | 
						|
					} | 
						|
				] | 
						|
			}; | 
						|
		} | 
						|
		if (!rule) { | 
						|
			throw new Error("Unexcepted null when object was expected as rule"); | 
						|
		} | 
						|
		if (typeof rule !== "object") { | 
						|
			throw new Error( | 
						|
				"Unexcepted " + | 
						|
					typeof rule + | 
						|
					" when object was expected as rule (" + | 
						|
					rule + | 
						|
					")" | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		const newRule = {}; | 
						|
		let useSource; | 
						|
		let resourceSource; | 
						|
		let condition; | 
						|
 | 
						|
		const checkUseSource = newSource => { | 
						|
			if (useSource && useSource !== newSource) { | 
						|
				throw new Error( | 
						|
					RuleSet.buildErrorMessage( | 
						|
						rule, | 
						|
						new Error( | 
						|
							"Rule can only have one result source (provided " + | 
						|
								newSource + | 
						|
								" and " + | 
						|
								useSource + | 
						|
								")" | 
						|
						) | 
						|
					) | 
						|
				); | 
						|
			} | 
						|
			useSource = newSource; | 
						|
		}; | 
						|
 | 
						|
		const checkResourceSource = newSource => { | 
						|
			if (resourceSource && resourceSource !== newSource) { | 
						|
				throw new Error( | 
						|
					RuleSet.buildErrorMessage( | 
						|
						rule, | 
						|
						new Error( | 
						|
							"Rule can only have one resource source (provided " + | 
						|
								newSource + | 
						|
								" and " + | 
						|
								resourceSource + | 
						|
								")" | 
						|
						) | 
						|
					) | 
						|
				); | 
						|
			} | 
						|
			resourceSource = newSource; | 
						|
		}; | 
						|
 | 
						|
		if (rule.test || rule.include || rule.exclude) { | 
						|
			checkResourceSource("test + include + exclude"); | 
						|
			condition = { | 
						|
				test: rule.test, | 
						|
				include: rule.include, | 
						|
				exclude: rule.exclude | 
						|
			}; | 
						|
			try { | 
						|
				newRule.resource = RuleSet.normalizeCondition(condition); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(condition, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.resource) { | 
						|
			checkResourceSource("resource"); | 
						|
			try { | 
						|
				newRule.resource = RuleSet.normalizeCondition(rule.resource); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(rule.resource, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.realResource) { | 
						|
			try { | 
						|
				newRule.realResource = RuleSet.normalizeCondition(rule.realResource); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(rule.realResource, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.resourceQuery) { | 
						|
			try { | 
						|
				newRule.resourceQuery = RuleSet.normalizeCondition(rule.resourceQuery); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(rule.resourceQuery, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.compiler) { | 
						|
			try { | 
						|
				newRule.compiler = RuleSet.normalizeCondition(rule.compiler); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(rule.compiler, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.issuer) { | 
						|
			try { | 
						|
				newRule.issuer = RuleSet.normalizeCondition(rule.issuer); | 
						|
			} catch (error) { | 
						|
				throw new Error(RuleSet.buildErrorMessage(rule.issuer, error)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.loader && rule.loaders) { | 
						|
			throw new Error( | 
						|
				RuleSet.buildErrorMessage( | 
						|
					rule, | 
						|
					new Error( | 
						|
						"Provided loader and loaders for rule (use only one of them)" | 
						|
					) | 
						|
				) | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		const loader = rule.loaders || rule.loader; | 
						|
		if (typeof loader === "string" && !rule.options && !rule.query) { | 
						|
			checkUseSource("loader"); | 
						|
			newRule.use = RuleSet.normalizeUse(loader.split("!"), ident); | 
						|
		} else if (typeof loader === "string" && (rule.options || rule.query)) { | 
						|
			checkUseSource("loader + options/query"); | 
						|
			newRule.use = RuleSet.normalizeUse( | 
						|
				{ | 
						|
					loader: loader, | 
						|
					options: rule.options, | 
						|
					query: rule.query | 
						|
				}, | 
						|
				ident | 
						|
			); | 
						|
		} else if (loader && (rule.options || rule.query)) { | 
						|
			throw new Error( | 
						|
				RuleSet.buildErrorMessage( | 
						|
					rule, | 
						|
					new Error( | 
						|
						"options/query cannot be used with loaders (use options for each array item)" | 
						|
					) | 
						|
				) | 
						|
			); | 
						|
		} else if (loader) { | 
						|
			checkUseSource("loaders"); | 
						|
			newRule.use = RuleSet.normalizeUse(loader, ident); | 
						|
		} else if (rule.options || rule.query) { | 
						|
			throw new Error( | 
						|
				RuleSet.buildErrorMessage( | 
						|
					rule, | 
						|
					new Error( | 
						|
						"options/query provided without loader (use loader + options)" | 
						|
					) | 
						|
				) | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		if (rule.use) { | 
						|
			checkUseSource("use"); | 
						|
			newRule.use = RuleSet.normalizeUse(rule.use, ident); | 
						|
		} | 
						|
 | 
						|
		if (rule.rules) { | 
						|
			newRule.rules = RuleSet.normalizeRules( | 
						|
				rule.rules, | 
						|
				refs, | 
						|
				`${ident}-rules` | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		if (rule.oneOf) { | 
						|
			newRule.oneOf = RuleSet.normalizeRules( | 
						|
				rule.oneOf, | 
						|
				refs, | 
						|
				`${ident}-oneOf` | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		const keys = Object.keys(rule).filter(key => { | 
						|
			return ![ | 
						|
				"resource", | 
						|
				"resourceQuery", | 
						|
				"compiler", | 
						|
				"test", | 
						|
				"include", | 
						|
				"exclude", | 
						|
				"issuer", | 
						|
				"loader", | 
						|
				"options", | 
						|
				"query", | 
						|
				"loaders", | 
						|
				"use", | 
						|
				"rules", | 
						|
				"oneOf" | 
						|
			].includes(key); | 
						|
		}); | 
						|
		for (const key of keys) { | 
						|
			newRule[key] = rule[key]; | 
						|
		} | 
						|
 | 
						|
		if (Array.isArray(newRule.use)) { | 
						|
			for (const item of newRule.use) { | 
						|
				if (item.ident) { | 
						|
					refs[item.ident] = item.options; | 
						|
				} | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		return newRule; | 
						|
	} | 
						|
 | 
						|
	static buildErrorMessage(condition, error) { | 
						|
		const conditionAsText = JSON.stringify( | 
						|
			condition, | 
						|
			(key, value) => { | 
						|
				return value === undefined ? "undefined" : value; | 
						|
			}, | 
						|
			2 | 
						|
		); | 
						|
		return error.message + " in " + conditionAsText; | 
						|
	} | 
						|
 | 
						|
	static normalizeUse(use, ident) { | 
						|
		if (typeof use === "function") { | 
						|
			return data => RuleSet.normalizeUse(use(data), ident); | 
						|
		} | 
						|
		if (Array.isArray(use)) { | 
						|
			return use | 
						|
				.map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`)) | 
						|
				.reduce((arr, items) => arr.concat(items), []); | 
						|
		} | 
						|
		return [RuleSet.normalizeUseItem(use, ident)]; | 
						|
	} | 
						|
 | 
						|
	static normalizeUseItemString(useItemString) { | 
						|
		const idx = useItemString.indexOf("?"); | 
						|
		if (idx >= 0) { | 
						|
			return { | 
						|
				loader: useItemString.substr(0, idx), | 
						|
				options: useItemString.substr(idx + 1) | 
						|
			}; | 
						|
		} | 
						|
		return { | 
						|
			loader: useItemString, | 
						|
			options: undefined | 
						|
		}; | 
						|
	} | 
						|
 | 
						|
	static normalizeUseItem(item, ident) { | 
						|
		if (typeof item === "string") { | 
						|
			return RuleSet.normalizeUseItemString(item); | 
						|
		} | 
						|
 | 
						|
		const newItem = {}; | 
						|
 | 
						|
		if (item.options && item.query) { | 
						|
			throw new Error("Provided options and query in use"); | 
						|
		} | 
						|
 | 
						|
		if (!item.loader) { | 
						|
			throw new Error("No loader specified"); | 
						|
		} | 
						|
 | 
						|
		newItem.options = item.options || item.query; | 
						|
 | 
						|
		if (typeof newItem.options === "object" && newItem.options) { | 
						|
			if (newItem.options.ident) { | 
						|
				newItem.ident = newItem.options.ident; | 
						|
			} else { | 
						|
				newItem.ident = ident; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		const keys = Object.keys(item).filter(function(key) { | 
						|
			return !["options", "query"].includes(key); | 
						|
		}); | 
						|
 | 
						|
		for (const key of keys) { | 
						|
			newItem[key] = item[key]; | 
						|
		} | 
						|
 | 
						|
		return newItem; | 
						|
	} | 
						|
 | 
						|
	static normalizeCondition(condition) { | 
						|
		if (!condition) throw new Error("Expected condition but got falsy value"); | 
						|
		if (typeof condition === "string") { | 
						|
			return str => str.indexOf(condition) === 0; | 
						|
		} | 
						|
		if (typeof condition === "function") { | 
						|
			return condition; | 
						|
		} | 
						|
		if (condition instanceof RegExp) { | 
						|
			return condition.test.bind(condition); | 
						|
		} | 
						|
		if (Array.isArray(condition)) { | 
						|
			const items = condition.map(c => RuleSet.normalizeCondition(c)); | 
						|
			return orMatcher(items); | 
						|
		} | 
						|
		if (typeof condition !== "object") { | 
						|
			throw Error( | 
						|
				"Unexcepted " + | 
						|
					typeof condition + | 
						|
					" when condition was expected (" + | 
						|
					condition + | 
						|
					")" | 
						|
			); | 
						|
		} | 
						|
 | 
						|
		const matchers = []; | 
						|
		Object.keys(condition).forEach(key => { | 
						|
			const value = condition[key]; | 
						|
			switch (key) { | 
						|
				case "or": | 
						|
				case "include": | 
						|
				case "test": | 
						|
					if (value) matchers.push(RuleSet.normalizeCondition(value)); | 
						|
					break; | 
						|
				case "and": | 
						|
					if (value) { | 
						|
						const items = value.map(c => RuleSet.normalizeCondition(c)); | 
						|
						matchers.push(andMatcher(items)); | 
						|
					} | 
						|
					break; | 
						|
				case "not": | 
						|
				case "exclude": | 
						|
					if (value) { | 
						|
						const matcher = RuleSet.normalizeCondition(value); | 
						|
						matchers.push(notMatcher(matcher)); | 
						|
					} | 
						|
					break; | 
						|
				default: | 
						|
					throw new Error("Unexcepted property " + key + " in condition"); | 
						|
			} | 
						|
		}); | 
						|
		if (matchers.length === 0) { | 
						|
			throw new Error("Excepted condition but got " + condition); | 
						|
		} | 
						|
		if (matchers.length === 1) { | 
						|
			return matchers[0]; | 
						|
		} | 
						|
		return andMatcher(matchers); | 
						|
	} | 
						|
 | 
						|
	exec(data) { | 
						|
		const result = []; | 
						|
		this._run( | 
						|
			data, | 
						|
			{ | 
						|
				rules: this.rules | 
						|
			}, | 
						|
			result | 
						|
		); | 
						|
		return result; | 
						|
	} | 
						|
 | 
						|
	_run(data, rule, result) { | 
						|
		// test conditions | 
						|
		if (rule.resource && !data.resource) return false; | 
						|
		if (rule.realResource && !data.realResource) return false; | 
						|
		if (rule.resourceQuery && !data.resourceQuery) return false; | 
						|
		if (rule.compiler && !data.compiler) return false; | 
						|
		if (rule.issuer && !data.issuer) return false; | 
						|
		if (rule.resource && !rule.resource(data.resource)) return false; | 
						|
		if (rule.realResource && !rule.realResource(data.realResource)) | 
						|
			return false; | 
						|
		if (data.issuer && rule.issuer && !rule.issuer(data.issuer)) return false; | 
						|
		if ( | 
						|
			data.resourceQuery && | 
						|
			rule.resourceQuery && | 
						|
			!rule.resourceQuery(data.resourceQuery) | 
						|
		) { | 
						|
			return false; | 
						|
		} | 
						|
		if (data.compiler && rule.compiler && !rule.compiler(data.compiler)) { | 
						|
			return false; | 
						|
		} | 
						|
 | 
						|
		// apply | 
						|
		const keys = Object.keys(rule).filter(key => { | 
						|
			return ![ | 
						|
				"resource", | 
						|
				"realResource", | 
						|
				"resourceQuery", | 
						|
				"compiler", | 
						|
				"issuer", | 
						|
				"rules", | 
						|
				"oneOf", | 
						|
				"use", | 
						|
				"enforce" | 
						|
			].includes(key); | 
						|
		}); | 
						|
		for (const key of keys) { | 
						|
			result.push({ | 
						|
				type: key, | 
						|
				value: rule[key] | 
						|
			}); | 
						|
		} | 
						|
 | 
						|
		if (rule.use) { | 
						|
			const process = use => { | 
						|
				if (typeof use === "function") { | 
						|
					process(use(data)); | 
						|
				} else if (Array.isArray(use)) { | 
						|
					use.forEach(process); | 
						|
				} else { | 
						|
					result.push({ | 
						|
						type: "use", | 
						|
						value: use, | 
						|
						enforce: rule.enforce | 
						|
					}); | 
						|
				} | 
						|
			}; | 
						|
			process(rule.use); | 
						|
		} | 
						|
 | 
						|
		if (rule.rules) { | 
						|
			for (let i = 0; i < rule.rules.length; i++) { | 
						|
				this._run(data, rule.rules[i], result); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		if (rule.oneOf) { | 
						|
			for (let i = 0; i < rule.oneOf.length; i++) { | 
						|
				if (this._run(data, rule.oneOf[i], result)) break; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		return true; | 
						|
	} | 
						|
 | 
						|
	findOptionsByIdent(ident) { | 
						|
		const options = this.references[ident]; | 
						|
		if (!options) { | 
						|
			throw new Error("Can't find options with ident '" + ident + "'"); | 
						|
		} | 
						|
		return options; | 
						|
	} | 
						|
};
 | 
						|
 |