<commit_before>'use strict';

const tsort = require('tsort');
const createAliasResolver = require('./aliases');
const UA = require('./UA');
const sourceslib = require('./sources').getCollection();
const path = require('path');
const appVersion = require(path.join(__dirname,'../package.json')).version;

// Load additional useragent features: primarily to use: agent.satisfies to
// test a browser version against a semver string
require('useragent/features');

function listAllPolyfills() {
	return sourceslib.listPolyfills();
}

function describePolyfill(featureName) {
	return sourceslib.getPolyfill(featureName);
}

function getOptions(opts) {
	return Object.assign({
		uaString: '',
		minify: true,
		unknown: 'ignore',
		features: {},
	}, opts);
}

/**
 * Given a set of features that should be polyfilled in 'options.features' (with flags i.e. `{<featurename>: {flags:[<flaglist>]}, ...}`), determine which have a configuration valid for the given options.uaString, and return a promise of set of canonical (unaliased) features (with flags) and polyfills.
 *
 * @param {object} options - Valid keys are uaString, minify, unknown and features
 * @return {promise} - Canonicalised feature definitions filtered for UA
 */
function getPolyfills(options) {

	options = getOptions(options);
	const ua = new UA(options.uaString);

	const resolveAliases = createAliasResolver([
		function aliasFromConfig(featureName) {
			return sourceslib.getConfigAliases(featureName);
		},
		function aliasAll(featureName) {
			return (featureName === 'all') ? sourceslib.listPolyfills() : undefined;
		}
	]);

	const resolveDependencies = createAliasResolver(
		function aliasDependencies(featureName) {
			return sourceslib.getPolyfill(featureName).then(function(polyfill) {
				return ((polyfill && polyfill.dependencies) || []).concat(featureName);
			});
		}
	);

	// Filter the features object to remove features not suitable for the current UA
	const filterForUATargeting = function(features) {
		const featuresList = Object.keys(features);
		return Promise.all(featuresList.map(featureName => {
			return sourceslib.getPolyfill(featureName).then(function(polyfill) {
				if (!polyfill) return false;

				const isBrowserMatch = (polyfill.browsers && polyfill.browsers[ua.getFamily()] && ua.satisfies(polyfill.browsers[ua.getFamily()]));
				const hasAlwaysFlagOverride = (features[featureName].flags.indexOf('always') !== -1);
				const unknownOverride = (options.unknown === 'polyfill' && ua.isUnknown());

				return (isBrowserMatch || hasAlwaysFlagOverride || unknownOverride) ? featureName : false;
			});
		})).then(filteredList => {
			return filteredList.reduce(function(out, key) {
				if (key) out[key] = features[key];
				return out;
			}, {});
		});
	};

	return Promise.resolve(options.features)
		.then(resolveAliases)
		.then(filterForUATargeting)
		.then(resolveDependencies)
		.then(filterForUATargeting)
	;
}

function getPolyfillString(options) {

	options = getOptions(options);
	const ua = new UA(options.uaString);
	const uaDebugName = ua.getFamily() + '/' + ua.getVersion() + ((ua.isUnknown() || !ua.meetsBaseline()) ? ' (unknown/unsupported; using policy `unknown='+options.unknown+'`)' : '');
	const lf = options.minify ? '' : '\n';
	let explainerComment = [];

	// Check UA and turn requested features into a list of polyfills
	return Promise.resolve()
		.then(() => {
			if (options.minify) {
				explainerComment.push('Rerun without minification (remove `.min` from URL path) for verbose metadata');
			} else {
				explainerComment.push(
					'Polyfill service v' + appVersion,
					'For detailed credits and licence information see http://github.com/financial-times/polyfill-service.',
					'',
					'UA detected: ' + uaDebugName,
					'Features requested: ' + Object.keys(options.features),
					''
				);
				if (!ua.meetsBaseline() && ua.getBaseline()) {
					explainerComment.push('Version range for polyfill support in this family is: ' + ua.getBaseline());
					explainerComment.push('');
				}
			}
			return ((!ua.meetsBaseline() || ua.isUnknown()) && options.unknown !== 'polyfill') ? {} : getPolyfills(options);
		})

		// Build a polyfill bundle of polyfill sources sorted in dependency order
		.then(features => {
			const warnings = {unknown:[], nopolyfill:[]};
			const graph = tsort();
			let builtPolyfillString = '';

			return Promise.all(Object.keys(features).map(featureName => {
				const feature = features[featureName];
				return sourceslib.getPolyfill(featureName).then(polyfill => {
					if (!polyfill) {
						warnings.unknown.push(' - '+featureName);
					} else {

						graph.add(featureName);

						const srcKey = (options.minify ? 'min':'raw') + 'Source';
						feature.polyfillOutput = polyfill[srcKey];

						if (feature.flags.indexOf('gated') !== -1 && polyfill.detectSource) {
							feature.polyfillOutput = "if (!(" + polyfill.detectSource + ")) {" + lf + feature.polyfillOutput + "}" + lf + lf;
						}

						if (polyfill.dependencies) {
							polyfill.dependencies.forEach(depName => {
								if (depName in features) {
									graph.add(depName, featureName);
								}
							});
						}

						feature.comment = '- ' + featureName + ', License: ' + (polyfill.license || 'CC0') + ((feature.aliasOf && feature.aliasOf.length) ? ' (required by "' + feature.aliasOf.join('", "') + '")' : '');
					}
				});
			}))
			.then(() => {

				// Using the graph order, build the completed polyfill bundle
				graph.sort().forEach(featureName => {
					builtPolyfillString += features[featureName].polyfillOutput || '';
					if (!options.minify && features[featureName].comment) {
						explainerComment.push(features[featureName].comment);
					}
				});
				if (builtPolyfillString) {

					// Outer closure hides private features from global scope
					builtPolyfillString = "(function(undefined) {" + lf + builtPolyfillString + lf + "})" + lf;

					// Invoke the closure, binding `this` to window (in a browser), self (in a web worker), or global (in Node/IOjs)
					builtPolyfillString += ".call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});";
				}

				if (warnings.unknown.length && !options.minify) {
					explainerComment = explainerComment.concat(
						'',
						'These features were not recognised:',
						warnings.unknown
					);
				}

				if (warnings.nopolyfill.length && !options.minify) {
					explainerComment = explainerComment.concat(
						'',
						'These features have no polyfill suitable for '+ua.getFamily() + '/' + ua.getVersion() + ':',
						warnings.nopolyfill
					);
				}

				return '/* ' + explainerComment.join('\n * ') + ' */\n\n' + builtPolyfillString;
			});
		})
		.catch(function(err) {
			console.log(err.stack || err);
		})
	;
}

module.exports = {
	describePolyfill: describePolyfill,
	listAllPolyfills: listAllPolyfills,
	getPolyfills: getPolyfills,
	getPolyfillString: getPolyfillString,
	normalizeUserAgent: UA.normalize,<commit_msg>Output warning when users invoke the all alias, see #655<commit_after>'use strict';

const tsort = require('tsort');
const createAliasResolver = require('./aliases');
const UA = require('./UA');
const sourceslib = require('./sources').getCollection();
const path = require('path');
const appVersion = require(path.join(__dirname,'../package.json')).version;

// Load additional useragent features: primarily to use: agent.satisfies to
// test a browser version against a semver string
require('useragent/features');

function listAllPolyfills() {
	return sourceslib.listPolyfills();
}

function describePolyfill(featureName) {
	return sourceslib.getPolyfill(featureName);
}

function getOptions(opts) {
	return Object.assign({
		uaString: '',
		minify: true,
		unknown: 'ignore',
		features: {},
	}, opts);
}

/**
 * Given a set of features that should be polyfilled in 'options.features' (with flags i.e. `{<featurename>: {flags:[<flaglist>]}, ...}`), determine which have a configuration valid for the given options.uaString, and return a promise of set of canonical (unaliased) features (with flags) and polyfills.
 *
 * @param {object} options - Valid keys are uaString, minify, unknown and features
 * @return {promise} - Canonicalised feature definitions filtered for UA
 */
function getPolyfills(options) {

	options = getOptions(options);
	const ua = new UA(options.uaString);

	const resolveAliases = createAliasResolver([
		function aliasFromConfig(featureName) {
			return sourceslib.getConfigAliases(featureName);
		},
		function aliasAll(featureName) {
			return (featureName === 'all') ? sourceslib.listPolyfills() : undefined;
		}
	]);

	const resolveDependencies = createAliasResolver(
		function aliasDependencies(featureName) {
			return sourceslib.getPolyfill(featureName).then(function(polyfill) {
				return ((polyfill && polyfill.dependencies) || []).concat(featureName);
			});
		}
	);

	// Filter the features object to remove features not suitable for the current UA
	const filterForUATargeting = function(features) {
		const featuresList = Object.keys(features);
		return Promise.all(featuresList.map(featureName => {
			return sourceslib.getPolyfill(featureName).then(function(polyfill) {
				if (!polyfill) return false;

				const isBrowserMatch = (polyfill.browsers && polyfill.browsers[ua.getFamily()] && ua.satisfies(polyfill.browsers[ua.getFamily()]));
				const hasAlwaysFlagOverride = (features[featureName].flags.indexOf('always') !== -1);
				const unknownOverride = (options.unknown === 'polyfill' && ua.isUnknown());

				return (isBrowserMatch || hasAlwaysFlagOverride || unknownOverride) ? featureName : false;
			});
		})).then(filteredList => {
			return filteredList.reduce(function(out, key) {
				if (key) out[key] = features[key];
				return out;
			}, {});
		});
	};

	return Promise.resolve(options.features)
		.then(resolveAliases)
		.then(filterForUATargeting)
		.then(resolveDependencies)
		.then(filterForUATargeting)
	;
}

function getPolyfillString(options) {

	options = getOptions(options);
	const ua = new UA(options.uaString);
	const uaDebugName = ua.getFamily() + '/' + ua.getVersion() + ((ua.isUnknown() || !ua.meetsBaseline()) ? ' (unknown/unsupported; using policy `unknown='+options.unknown+'`)' : '');
	const lf = options.minify ? '' : '\n';
	let explainerComment = [];

	// Check UA and turn requested features into a list of polyfills
	return Promise.resolve()
		.then(() => {
			if (options.minify) {
				explainerComment.push('Rerun without minification (remove `.min` from URL path) for verbose metadata');
			} else {
				explainerComment.push(
					'Polyfill service v' + appVersion,
					'For detailed credits and licence information see http://github.com/financial-times/polyfill-service.',
					'',
					'UA detected: ' + uaDebugName,
					'Features requested: ' + Object.keys(options.features),
					''
				);
				if (!ua.meetsBaseline() && ua.getBaseline()) {
					explainerComment.push('Version range for polyfill support in this family is: ' + ua.getBaseline());
					explainerComment.push('');
				}
			}
			return ((!ua.meetsBaseline() || ua.isUnknown()) && options.unknown !== 'polyfill') ? {} : getPolyfills(options);
		})

		// Build a polyfill bundle of polyfill sources sorted in dependency order
		.then(features => {
			const warnings = {unknown:[], nopolyfill:[]};
			const graph = tsort();
			let builtPolyfillString = '';

			return Promise.all(Object.keys(features).map(featureName => {
				const feature = features[featureName];
				return sourceslib.getPolyfill(featureName).then(polyfill => {
					if (!polyfill) {
						warnings.unknown.push(' - '+featureName);
					} else {

						graph.add(featureName);

						const srcKey = (options.minify ? 'min':'raw') + 'Source';
						feature.polyfillOutput = polyfill[srcKey];

						if (feature.flags.indexOf('gated') !== -1 && polyfill.detectSource) {
							feature.polyfillOutput = "if (!(" + polyfill.detectSource + ")) {" + lf + feature.polyfillOutput + "}" + lf + lf;
						}

						if (polyfill.dependencies) {
							polyfill.dependencies.forEach(depName => {
								if (depName in features) {
									graph.add(depName, featureName);
								}
							});
						}

						feature.comment = '- ' + featureName + ', License: ' + (polyfill.license || 'CC0') + ((feature.aliasOf && feature.aliasOf.length) ? ' (required by "' + feature.aliasOf.join('", "') + '")' : '');
					}
				});
			}))
			.then(() => {

				// Using the graph order, build the completed polyfill bundle
				graph.sort().forEach(featureName => {
					builtPolyfillString += features[featureName].polyfillOutput || '';
					if (!options.minify && features[featureName].comment) {
						explainerComment.push(features[featureName].comment);
					}
				});
				if (builtPolyfillString) {

					// Outer closure hides private features from global scope
					builtPolyfillString = "(function(undefined) {" + lf + builtPolyfillString + lf + "})" + lf;

					// Invoke the closure, binding `this` to window (in a browser), self (in a web worker), or global (in Node/IOjs)
					builtPolyfillString += ".call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});";
				}

				if (warnings.unknown.length && !options.minify) {
					explainerComment = explainerComment.concat(
						'',
						'These features were not recognised:',
						warnings.unknown
					);
				}

				if (warnings.nopolyfill.length && !options.minify) {
					explainerComment = explainerComment.concat(
						'',
						'These features have no polyfill suitable for '+ua.getFamily() + '/' + ua.getVersion() + ':',
						warnings.nopolyfill
					);
				}

				if ('all' in options.features) {
					let warnText = 'Using the `all` alias with polyfill.io is a very bad idea. In a future version of the service, `all` will deliver the same behaviour as `default`, so we recommend using default instead.';
					explainerComment.push('', warnText);
					builtPolyfillString += "\nconsole.log('"+warnText+"');\n";
				}

				return '/* ' + explainerComment.join('\n * ') + ' */\n\n' + builtPolyfillString;
			});
		})
		.catch(function(err) {
			console.log(err.stack || err);
		})
	;
}

module.exports = {
	describePolyfill: describePolyfill,
	listAllPolyfills: listAllPolyfills,
	getPolyfills: getPolyfills,
	getPolyfillString: getPolyfillString,
	normalizeUserAgent: UA.normalize,
{'fraction_non_alphanumeric': 0.14801993907504846, 'fraction_numerical': 0.0006230960952644697, 'mean_word_length': 3.8538306451612905, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3041382', 'n_tokens_mistral': 5105, 'n_tokens_neox': 4284, 'n_words': 1016}