src/core/core.config.js
File `core.config.js` has 310 lines of code (exceeds 250 allowed). Consider refactoring.import defaults, {overrides, descriptors} from './core.defaults.js';import {mergeIf, resolveObjectKey, isArray, isFunction, valueOrDefault, isObject} from '../helpers/helpers.core.js';import {_attachContext, _createResolver, _descriptors} from '../helpers/helpers.config.js'; export function getIndexAxis(type, options) { const datasetDefaults = defaults.datasets[type] || {}; const datasetOptions = (options.datasets || {})[type] || {}; return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';} function getAxisFromDefaultScaleID(id, indexAxis) { let axis = id; if (id === '_index_') { axis = indexAxis; } else if (id === '_value_') { axis = indexAxis === 'x' ? 'y' : 'x'; } return axis;} function getDefaultScaleIDFromAxis(axis, indexAxis) { return axis === indexAxis ? '_index_' : '_value_';} function idMatchesAxis(id) { if (id === 'x' || id === 'y' || id === 'r') { return id; }} function axisFromPosition(position) { if (position === 'top' || position === 'bottom') { return 'x'; } if (position === 'left' || position === 'right') { return 'y'; }} export function determineAxis(id, ...scaleOptions) { if (idMatchesAxis(id)) { return id; } for (const opts of scaleOptions) { const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase()); if (axis) { return axis; } } throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`);} function getAxisFromDataset(id, axis, dataset) { if (dataset[axis + 'AxisID'] === id) { return {axis}; }} function retrieveAxisFromDatasets(id, config) { if (config.data && config.data.datasets) { const boundDs = config.data.datasets.filter((d) => d.xAxisID === id || d.yAxisID === id); if (boundDs.length) { return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]); } } return {};} Function `mergeScaleConfig` has 34 lines of code (exceeds 25 allowed). Consider refactoring.function mergeScaleConfig(config, options) { const chartDefaults = overrides[config.type] || {scales: {}}; const configScales = options.scales || {}; const chartIndexAxis = getIndexAxis(config.type, options); const scales = Object.create(null); // First figure out first scale id's per axis. Object.keys(configScales).forEach(id => { const scaleConf = configScales[id]; if (!isObject(scaleConf)) { return console.error(`Invalid scale configuration for scale: ${id}`); } if (scaleConf._proxy) { return console.warn(`Ignoring resolver passed as options for scale: ${id}`); } const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]); const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); const defaultScaleOptions = chartDefaults.scales || {}; scales[id] = mergeIf(Object.create(null), [{axis}, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]); }); // Then merge dataset defaults to scale configs config.data.datasets.forEach(dataset => { const type = dataset.type || config.type; const indexAxis = dataset.indexAxis || getIndexAxis(type, options); const datasetDefaults = overrides[type] || {}; const defaultScaleOptions = datasetDefaults.scales || {}; Object.keys(defaultScaleOptions).forEach(defaultID => { const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); const id = dataset[axis + 'AxisID'] || axis; scales[id] = scales[id] || Object.create(null); mergeIf(scales[id], [{axis}, configScales[id], defaultScaleOptions[defaultID]]); }); }); // apply scale defaults, if not overridden by dataset defaults Object.keys(scales).forEach(key => { const scale = scales[key]; mergeIf(scale, [defaults.scales[scale.type], defaults.scale]); }); return scales;} function initOptions(config) { const options = config.options || (config.options = {}); options.plugins = valueOrDefault(options.plugins, {}); options.scales = mergeScaleConfig(config, options);} function initData(data) { data = data || {}; data.datasets = data.datasets || []; data.labels = data.labels || []; return data;} function initConfig(config) { config = config || {}; config.data = initData(config.data); initOptions(config); return config;} const keyCache = new Map();const keysCached = new Set(); function cachedKeys(cacheKey, generate) { let keys = keyCache.get(cacheKey); if (!keys) { keys = generate(); keyCache.set(cacheKey, keys); keysCached.add(keys); } return keys;} const addIfFound = (set, obj, key) => { const opts = resolveObjectKey(obj, key); if (opts !== undefined) { set.add(opts); }}; export default class Config { constructor(config) { this._config = initConfig(config); this._scopeCache = new Map(); this._resolverCache = new Map(); } get platform() { return this._config.platform; } get type() { return this._config.type; } set type(type) { this._config.type = type; } get data() { return this._config.data; } set data(data) { this._config.data = initData(data); } get options() { return this._config.options; } set options(options) { this._config.options = options; } get plugins() { return this._config.plugins; } update() { const config = this._config; this.clearCache(); initOptions(config); } clearCache() { this._scopeCache.clear(); this._resolverCache.clear(); } /** * Returns the option scope keys for resolving dataset options. * These keys do not include the dataset itself, because it is not under options. * @param {string} datasetType * @return {string[][]} */ datasetScopeKeys(datasetType) { return cachedKeys(datasetType, () => [[ `datasets.${datasetType}`, '' ]]); } /** * Returns the option scope keys for resolving dataset animation options. * These keys do not include the dataset itself, because it is not under options. * @param {string} datasetType * @param {string} transition * @return {string[][]} */ datasetAnimationScopeKeys(datasetType, transition) { return cachedKeys(`${datasetType}.transition.${transition}`, () => [ [ `datasets.${datasetType}.transitions.${transition}`, `transitions.${transition}`, ], // The following are used for looking up the `animations` and `animation` keys [ `datasets.${datasetType}`, '' ] ]); } /** * Returns the options scope keys for resolving element options that belong * to an dataset. These keys do not include the dataset itself, because it * is not under options. * @param {string} datasetType * @param {string} elementType * @return {string[][]} */ datasetElementScopeKeys(datasetType, elementType) { return cachedKeys(`${datasetType}-${elementType}`, () => [[ `datasets.${datasetType}.elements.${elementType}`, `datasets.${datasetType}`, `elements.${elementType}`, '' ]]); } /** * Returns the options scope keys for resolving plugin options. * @param {{id: string, additionalOptionScopes?: string[]}} plugin * @return {string[][]} */ pluginScopeKeys(plugin) { const id = plugin.id; const type = this.type; return cachedKeys(`${type}-plugin-${id}`, () => [[ `plugins.${id}`, ...plugin.additionalOptionScopes || [], ]]); } /** * @private */ _cachedScopes(mainScope, resetCache) { const _scopeCache = this._scopeCache; let cache = _scopeCache.get(mainScope); if (!cache || resetCache) { cache = new Map(); _scopeCache.set(mainScope, cache); } return cache; } /** * Resolves the objects from options and defaults for option value resolution. * @param {object} mainScope - The main scope object for options * @param {string[][]} keyLists - The arrays of keys in resolution order * @param {boolean} [resetCache] - reset the cache for this mainScope */ getOptionScopes(mainScope, keyLists, resetCache) { const {options, type} = this; const cache = this._cachedScopes(mainScope, resetCache); const cached = cache.get(keyLists); if (cached) { return cached; } const scopes = new Set(); keyLists.forEach(keys => { if (mainScope) { scopes.add(mainScope); keys.forEach(key => addIfFound(scopes, mainScope, key)); } keys.forEach(key => addIfFound(scopes, options, key)); keys.forEach(key => addIfFound(scopes, overrides[type] || {}, key)); keys.forEach(key => addIfFound(scopes, defaults, key)); keys.forEach(key => addIfFound(scopes, descriptors, key)); }); const array = Array.from(scopes); if (array.length === 0) { array.push(Object.create(null)); } if (keysCached.has(keyLists)) { cache.set(keyLists, array); } return array; } /** * Returns the option scopes for resolving chart options * @return {object[]} */ chartOptionScopes() { const {options, type} = this; return [ options, overrides[type] || {}, defaults.datasets[type] || {}, // https://github.com/chartjs/Chart.js/issues/8531 {type}, defaults, descriptors ]; } /** * @param {object[]} scopes * @param {string[]} names * @param {function|object} context * @param {string[]} [prefixes] * @return {object} */ resolveNamedOptions(scopes, names, context, prefixes = ['']) { const result = {$shared: true}; const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes); let options = resolver; if (needContext(resolver, names)) { result.$shared = false; context = isFunction(context) ? context() : context; // subResolver is passed to scriptable options. It should not resolve to hover options. const subResolver = this.createResolver(scopes, context, subPrefixes); options = _attachContext(resolver, context, subResolver); } for (const prop of names) { result[prop] = options[prop]; } return result; } /** * @param {object[]} scopes * @param {object} [context] * @param {string[]} [prefixes] * @param {{scriptable: boolean, indexable: boolean, allKeys?: boolean}} [descriptorDefaults] */ createResolver(scopes, context, prefixes = [''], descriptorDefaults) { const {resolver} = getResolver(this._resolverCache, scopes, prefixes); return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver; }} function getResolver(resolverCache, scopes, prefixes) { let cache = resolverCache.get(scopes); if (!cache) { cache = new Map(); resolverCache.set(scopes, cache); } const cacheKey = prefixes.join(); let cached = cache.get(cacheKey); if (!cached) { const resolver = _createResolver(scopes, prefixes); cached = { resolver, subPrefixes: prefixes.filter(p => !p.toLowerCase().includes('hover')) }; cache.set(cacheKey, cached); } return cached;} const hasFunction = value => isObject(value) && Object.getOwnPropertyNames(value).some((key) => isFunction(value[key])); function needContext(proxy, names) { const {isScriptable, isIndexable} = _descriptors(proxy); for (const prop of names) { const scriptable = isScriptable(prop); const indexable = isIndexable(prop); const value = (indexable || scriptable) && proxy[prop]; if ((scriptable && (isFunction(value) || hasFunction(value))) || (indexable && isArray(value))) { return true; } } return false;}