-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5339 from Polymer/5262-kschaaf-legacy-undefined-3.x
[3.x] Add legacy-data-mixin as 1.x->2.x/3.x migration aide. Fixes #5262.
- Loading branch information
Showing
6 changed files
with
1,020 additions
and
478 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/** | ||
@license | ||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved. | ||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt | ||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt | ||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt | ||
Code distributed by Google as part of the polymer project is also | ||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt | ||
*/ | ||
|
||
import { Class } from './class.js'; | ||
import { Polymer } from '../../polymer-legacy.js'; | ||
import { dedupingMixin } from '../utils/mixin.js'; | ||
|
||
const UndefinedArgumentError = class extends Error { | ||
constructor(message, arg) { | ||
super(message); | ||
this.arg = arg; | ||
this.name = this.constructor.name; | ||
// Affordances for ensuring instanceof works after babel ES5 compilation | ||
// TODO(kschaaf): Remove after polymer CLI updates to newer Babel that | ||
// sets the constructor/prototype correctly for subclassed builtins | ||
this.constructor = UndefinedArgumentError; | ||
this.__proto__ = UndefinedArgumentError.prototype; | ||
} | ||
}; | ||
|
||
/** | ||
* Wraps effect functions to catch `UndefinedArgumentError`s and warn. | ||
* | ||
* @param {Object=} effect Effect metadata object | ||
* @param {Object=} fnName Name of user function, if known | ||
* @return {?Object} Effect metadata object | ||
*/ | ||
function wrapEffect(effect, fnName) { | ||
if (effect && effect.fn) { | ||
const fn = effect.fn; | ||
effect.fn = function() { | ||
try { | ||
fn.apply(this, arguments); | ||
} catch (e) { | ||
if (e instanceof UndefinedArgumentError) { | ||
console.warn(`Argument '${e.arg}'${fnName ?` for method '${fnName}'` : ''} was undefined. Ensure it has an undefined check.`); | ||
} else { | ||
throw e; | ||
} | ||
} | ||
}; | ||
} | ||
return effect; | ||
} | ||
|
||
/** | ||
* Mixin to selectively add back Polymer 1.x's `undefined` rules | ||
* governing when observers & computing functions run based | ||
* on all arguments being defined (reference https://www.polymer-project.org/1.0/docs/devguide/observers#multi-property-observers). | ||
* | ||
* When loaded, all legacy elements (defined with `Polymer({...})`) | ||
* will have the mixin applied. The mixin only restores legacy data handling | ||
* if `_legacyUndefinedCheck: true` is set on the element's prototype. | ||
* | ||
* This mixin is intended for use to help migration from Polymer 1.x to | ||
* 2.x+ by allowing legacy code to work while identifying observers and | ||
* computing functions that need undefined checks to work without | ||
* the mixin in Polymer 2. | ||
* | ||
* @mixinFunction | ||
* @polymer | ||
* @summary Mixin to selectively add back Polymer 1.x's `undefined` rules | ||
* governing when observers & computing functions run. | ||
*/ | ||
export const LegacyDataMixin = dedupingMixin(superClass => { | ||
|
||
/** | ||
* @constructor | ||
* @extends {superClass} | ||
* @unrestricted | ||
* @private */ | ||
class LegacyDataMixin extends superClass { | ||
/** | ||
* Overrides `Polymer.PropertyEffects` to add `undefined` argument | ||
* checking to match Polymer 1.x style rules | ||
* | ||
* @param {!Array<!MethodArg>} args Array of argument metadata | ||
* @param {string} path Property/path name that triggered the method effect | ||
* @param {Object} props Bag of current property changes | ||
* @return {Array<*>} Array of argument values | ||
* @private | ||
*/ | ||
_marshalArgs(args, path, props) { | ||
const vals = super._marshalArgs(args, path, props); | ||
// Per legacy data rules, single-property observers (whether in `properties` | ||
// and in `observers`) are called regardless of whether their argument is | ||
// undefined or not. Multi-property observers must have all arguments defined | ||
if (this._legacyUndefinedCheck && vals.length > 1) { | ||
for (let i=0; i<vals.length; i++) { | ||
if (vals[i] === undefined) { | ||
// Break out of effect's control flow; will be caught in | ||
// wrapped property effect function below | ||
const name = args[i].name; | ||
throw new UndefinedArgumentError(`Argument '${name}' is undefined. Ensure it has an undefined check.`, name); | ||
} | ||
} | ||
} | ||
return vals; | ||
} | ||
|
||
/** | ||
* Overrides `Polyer.PropertyEffects` to wrap effect functions to | ||
* catch `UndefinedArgumentError`s and warn. | ||
* | ||
* @param {string} property Property that should trigger the effect | ||
* @param {string} type Effect type, from this.PROPERTY_EFFECT_TYPES | ||
* @param {Object=} effect Effect metadata object | ||
* @return {void} | ||
* @protected | ||
*/ | ||
_addPropertyEffect(property, type, effect) { | ||
return super._addPropertyEffect(property, type, | ||
wrapEffect(effect, effect && effect.info && effect.info.methodName)); | ||
} | ||
|
||
/** | ||
* Overrides `Polyer.PropertyEffects` to wrap effect functions to | ||
* catch `UndefinedArgumentError`s and warn. | ||
* | ||
* @param {Object} templateInfo Template metadata to add effect to | ||
* @param {string} prop Property that should trigger the effect | ||
* @param {Object=} effect Effect metadata object | ||
* @return {void} | ||
* @protected | ||
*/ | ||
static _addTemplatePropertyEffect(templateInfo, prop, effect) { | ||
return super._addTemplatePropertyEffect(templateInfo, prop, wrapEffect(effect)); | ||
} | ||
|
||
} | ||
|
||
return LegacyDataMixin; | ||
|
||
}); | ||
|
||
// LegacyDataMixin is applied to base class _before_ metaprogramming, to | ||
// ensure override of _addPropertyEffect et.al. are used by metaprogramming | ||
// performed in _finalizeClass | ||
Polymer.Class = (info, mixin) => Class(info, | ||
superClass => mixin ? | ||
mixin(LegacyDataMixin(superClass)) : | ||
LegacyDataMixin(superClass) | ||
); | ||
|
||
console.info('LegacyDataMixin will be applied to all legacy elements.\n' + | ||
'Set `_legacyUndefinedCheck: true` on element class to enable.'); |
Oops, something went wrong.