1import config from '../config'2import { warn } from './debug'3import { set } from '../observer/index'4import { unicodeRegExp } from './lang'5import { nativeWatch, hasSymbol } from './env'6import { isArray, isFunction } from 'shared/util'78import { ASSET_TYPES, LIFECYCLE_HOOKS } from 'shared/constants'910import {11 extend,12 hasOwn,13 camelize,14 toRawType,15 capitalize,16 isBuiltInTag,17 isPlainObject18} from 'shared/util'19import type { Component } from 'types/component'20import type { ComponentOptions } from 'types/options'2122/**23 * Option overwriting strategies are functions that handle24 * how to merge a parent option value and a child option25 * value into the final value.26 */27const strats = config.optionMergeStrategies2829/**30 * Options with restrictions31 */32if (__DEV__) {33 strats.el = strats.propsData = function (34 parent: any,35 child: any,36 vm: any,37 key: any38 ) {39 if (!vm) {40 warn(41 `option "${key}" can only be used during instance ` +42 'creation with the `new` keyword.'43 )44 }45 return defaultStrat(parent, child)46 }47}4849/**50 * Helper that recursively merges two data objects together.51 */52function mergeData(53 to: Record<string | symbol, any>,54 from: Record<string | symbol, any> | null,55 recursive = true56): Record<PropertyKey, any> {57 if (!from) return to58 let key, toVal, fromVal5960 const keys = hasSymbol61 ? (Reflect.ownKeys(from) as string[])62 : Object.keys(from)6364 for (let i = 0; i < keys.length; i++) {65 key = keys[i]66 // in case the object is already observed...67 if (key === '__ob__') continue68 toVal = to[key]69 fromVal = from[key]70 if (!recursive || !hasOwn(to, key)) {71 set(to, key, fromVal)72 } else if (73 toVal !== fromVal &&74 isPlainObject(toVal) &&75 isPlainObject(fromVal)76 ) {77 mergeData(toVal, fromVal)78 }79 }80 return to81}8283/**84 * Data85 */86export function mergeDataOrFn(87 parentVal: any,88 childVal: any,89 vm?: Component90): Function | null {91 if (!vm) {92 // in a Vue.extend merge, both should be functions93 if (!childVal) {94 return parentVal95 }96 if (!parentVal) {97 return childVal98 }99 // when parentVal & childVal are both present,100 // we need to return a function that returns the101 // merged result of both functions... no need to102 // check if parentVal is a function here because103 // it has to be a function to pass previous merges.104 return function mergedDataFn() {105 return mergeData(106 isFunction(childVal) ? childVal.call(this, this) : childVal,107 isFunction(parentVal) ? parentVal.call(this, this) : parentVal108 )109 }110 } else {111 return function mergedInstanceDataFn() {112 // instance merge113 const instanceData = isFunction(childVal)114 ? childVal.call(vm, vm)115 : childVal116 const defaultData = isFunction(parentVal)117 ? parentVal.call(vm, vm)118 : parentVal119 if (instanceData) {120 return mergeData(instanceData, defaultData)121 } else {122 return defaultData123 }124 }125 }126}127128strats.data = function (129 parentVal: any,130 childVal: any,131 vm?: Component132): Function | null {133 if (!vm) {134 if (childVal && typeof childVal !== 'function') {135 __DEV__ &&136 warn(137 'The "data" option should be a function ' +138 'that returns a per-instance value in component ' +139 'definitions.',140 vm141 )142143 return parentVal144 }145 return mergeDataOrFn(parentVal, childVal)146 }147148 return mergeDataOrFn(parentVal, childVal, vm)149}150151/**152 * Hooks and props are merged as arrays.153 */154export function mergeLifecycleHook(155 parentVal: Array<Function> | null,156 childVal: Function | Array<Function> | null157): Array<Function> | null {158 const res = childVal159 ? parentVal160 ? parentVal.concat(childVal)161 : isArray(childVal)162 ? childVal163 : [childVal]164 : parentVal165 return res ? dedupeHooks(res) : res166}167168function dedupeHooks(hooks: any) {169 const res: Array<any> = []170 for (let i = 0; i < hooks.length; i++) {171 if (res.indexOf(hooks[i]) === -1) {172 res.push(hooks[i])173 }174 }175 return res176}177178LIFECYCLE_HOOKS.forEach(hook => {179 strats[hook] = mergeLifecycleHook180})181182/**183 * Assets184 *185 * When a vm is present (instance creation), we need to do186 * a three-way merge between constructor options, instance187 * options and parent options.188 */189function mergeAssets(190 parentVal: Object | null,191 childVal: Object | null,192 vm: Component | null,193 key: string194): Object {195 const res = Object.create(parentVal || null)196 if (childVal) {197 __DEV__ && assertObjectType(key, childVal, vm)198 return extend(res, childVal)199 } else {200 return res201 }202}203204ASSET_TYPES.forEach(function (type) {205 strats[type + 's'] = mergeAssets206})207208/**209 * Watchers.210 *211 * Watchers hashes should not overwrite one212 * another, so we merge them as arrays.213 */214strats.watch = function (215 parentVal: Record<string, any> | null,216 childVal: Record<string, any> | null,217 vm: Component | null,218 key: string219): Object | null {220 // work around Firefox's Object.prototype.watch...221 //@ts-expect-error work around222 if (parentVal === nativeWatch) parentVal = undefined223 //@ts-expect-error work around224 if (childVal === nativeWatch) childVal = undefined225 /* istanbul ignore if */226 if (!childVal) return Object.create(parentVal || null)227 if (__DEV__) {228 assertObjectType(key, childVal, vm)229 }230 if (!parentVal) return childVal231 const ret: Record<string, any> = {}232 extend(ret, parentVal)233 for (const key in childVal) {234 let parent = ret[key]235 const child = childVal[key]236 if (parent && !isArray(parent)) {237 parent = [parent]238 }239 ret[key] = parent ? parent.concat(child) : isArray(child) ? child : [child]240 }241 return ret242}243244/**245 * Other object hashes.246 */247strats.props =248 strats.methods =249 strats.inject =250 strats.computed =251 function (252 parentVal: Object | null,253 childVal: Object | null,254 vm: Component | null,255 key: string256 ): Object | null {257 if (childVal && __DEV__) {258 assertObjectType(key, childVal, vm)259 }260 if (!parentVal) return childVal261 const ret = Object.create(null)262 extend(ret, parentVal)263 if (childVal) extend(ret, childVal)264 return ret265 }266267strats.provide = function (parentVal: Object | null, childVal: Object | null) {268 if (!parentVal) return childVal269 return function () {270 const ret = Object.create(null)271 mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal)272 if (childVal) {273 mergeData(274 ret,275 isFunction(childVal) ? childVal.call(this) : childVal,276 false // non-recursive277 )278 }279 return ret280 }281}282283/**284 * Default strategy.285 */286const defaultStrat = function (parentVal: any, childVal: any): any {287 return childVal === undefined ? parentVal : childVal288}289290/**291 * Validate component names292 */293function checkComponents(options: Record<string, any>) {294 for (const key in options.components) {295 validateComponentName(key)296 }297}298299export function validateComponentName(name: string) {300 if (301 !new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)302 ) {303 warn(304 'Invalid component name: "' +305 name +306 '". Component names ' +307 'should conform to valid custom element name in html5 specification.'308 )309 }310 if (isBuiltInTag(name) || config.isReservedTag(name)) {311 warn(312 'Do not use built-in or reserved HTML elements as component ' +313 'id: ' +314 name315 )316 }317}318319/**320 * Ensure all props option syntax are normalized into the321 * Object-based format.322 */323function normalizeProps(options: Record<string, any>, vm?: Component | null) {324 const props = options.props325 if (!props) return326 const res: Record<string, any> = {}327 let i, val, name328 if (isArray(props)) {329 i = props.length330 while (i--) {331 val = props[i]332 if (typeof val === 'string') {333 name = camelize(val)334 res[name] = { type: null }335 } else if (__DEV__) {336 warn('props must be strings when using array syntax.')337 }338 }339 } else if (isPlainObject(props)) {340 for (const key in props) {341 val = props[key]342 name = camelize(key)343 res[name] = isPlainObject(val) ? val : { type: val }344 }345 } else if (__DEV__) {346 warn(347 `Invalid value for option "props": expected an Array or an Object, ` +348 `but got ${toRawType(props)}.`,349 vm350 )351 }352 options.props = res353}354355/**356 * Normalize all injections into Object-based format357 */358function normalizeInject(options: Record<string, any>, vm?: Component | null) {359 const inject = options.inject360 if (!inject) return361 const normalized: Record<string, any> = (options.inject = {})362 if (isArray(inject)) {363 for (let i = 0; i < inject.length; i++) {364 normalized[inject[i]] = { from: inject[i] }365 }366 } else if (isPlainObject(inject)) {367 for (const key in inject) {368 const val = inject[key]369 normalized[key] = isPlainObject(val)370 ? extend({ from: key }, val)371 : { from: val }372 }373 } else if (__DEV__) {374 warn(375 `Invalid value for option "inject": expected an Array or an Object, ` +376 `but got ${toRawType(inject)}.`,377 vm378 )379 }380}381382/**383 * Normalize raw function directives into object format.384 */385function normalizeDirectives(options: Record<string, any>) {386 const dirs = options.directives387 if (dirs) {388 for (const key in dirs) {389 const def = dirs[key]390 if (isFunction(def)) {391 dirs[key] = { bind: def, update: def }392 }393 }394 }395}396397function assertObjectType(name: string, value: any, vm: Component | null) {398 if (!isPlainObject(value)) {399 warn(400 `Invalid value for option "${name}": expected an Object, ` +401 `but got ${toRawType(value)}.`,402 vm403 )404 }405}406407/**408 * Merge two option objects into a new one.409 * Core utility used in both instantiation and inheritance.410 */411export function mergeOptions(412 parent: Record<string, any>,413 child: Record<string, any>,414 vm?: Component | null415): ComponentOptions {416 if (__DEV__) {417 checkComponents(child)418 }419420 if (isFunction(child)) {421 // @ts-expect-error422 child = child.options423 }424425 normalizeProps(child, vm)426 normalizeInject(child, vm)427 normalizeDirectives(child)428429 // Apply extends and mixins on the child options,430 // but only if it is a raw options object that isn't431 // the result of another mergeOptions call.432 // Only merged options has the _base property.433 if (!child._base) {434 if (child.extends) {435 parent = mergeOptions(parent, child.extends, vm)436 }437 if (child.mixins) {438 for (let i = 0, l = child.mixins.length; i < l; i++) {439 parent = mergeOptions(parent, child.mixins[i], vm)440 }441 }442 }443444 const options: ComponentOptions = {} as any445 let key446 for (key in parent) {447 mergeField(key)448 }449 for (key in child) {450 if (!hasOwn(parent, key)) {451 mergeField(key)452 }453 }454 function mergeField(key: any) {455 const strat = strats[key] || defaultStrat456 options[key] = strat(parent[key], child[key], vm, key)457 }458 return options459}460461/**462 * Resolve an asset.463 * This function is used because child instances need access464 * to assets defined in its ancestor chain.465 */466export function resolveAsset(467 options: Record<string, any>,468 type: string,469 id: string,470 warnMissing?: boolean471): any {472 /* istanbul ignore if */473 if (typeof id !== 'string') {474 return475 }476 const assets = options[type]477 // check local registration variations first478 if (hasOwn(assets, id)) return assets[id]479 const camelizedId = camelize(id)480 if (hasOwn(assets, camelizedId)) return assets[camelizedId]481 const PascalCaseId = capitalize(camelizedId)482 if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]483 // fallback to prototype chain484 const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]485 if (__DEV__ && warnMissing && !res) {486 warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id)487 }488 return res489}
Findings
✓ No findings reported for this file.