Skip to content

Commit

Permalink
Add ugly type assertions for this (#659)
Browse files Browse the repository at this point in the history
* Add ugly type assertions for `this`

Because of google/closure-compiler#3340 – `this` has an unknown type to closure compiler in static methods. In order to be compatible with the Closure Compiler + JSConformance `BanUnknownThis` check, we must explicitly add type annotations wherever we access `this` in a static method.

Yes, this is pretty awful from a legibility perspective. We could improve legibility at the cost of additional size by doing:

```typescript
const self = this as typeof LitElement;
```

I went with the zero-size-cost option here.

This required disabling the unnecessary type assertions lint pass :(
  • Loading branch information
rictic authored Apr 18, 2019
1 parent 2bf7672 commit f552fc4
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 287 deletions.
15 changes: 8 additions & 7 deletions src/lib/css-tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const unsafeCSS = (value: unknown) => {
return new CSSResult(String(value), constructionToken);
};

const textFromCSSResult = (value: CSSResult | number) => {
const textFromCSSResult = (value: CSSResult|number) => {
if (value instanceof CSSResult) {
return value.cssText;
} else if (typeof value === 'number') {
Expand All @@ -79,9 +79,10 @@ const textFromCSSResult = (value: CSSResult | number) => {
* used. To incorporate non-literal values `unsafeCSS` may be used inside a
* template string part.
*/
export const css = (strings: TemplateStringsArray, ...values: (CSSResult | number)[]) => {
const cssText = values.reduce(
(acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],
strings[0]);
return new CSSResult(cssText, constructionToken);
};
export const css =
(strings: TemplateStringsArray, ...values: (CSSResult|number)[]) => {
const cssText = values.reduce(
(acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],
strings[0]);
return new CSSResult(cssText, constructionToken);
};
5 changes: 2 additions & 3 deletions src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ const standardProperty =
// initializer: descriptor.initializer,
// }
// ],
// tslint:disable-next-line:no-any decorator
initializer(this: any) {
initializer(this: {[key: string]: unknown}) {
if (typeof element.initializer === 'function') {
this[element.key] = element.initializer.call(this);
this[element.key as string] = element.initializer.call(this);
}
},
finisher(clazz: typeof UpdatingElement) {
Expand Down
73 changes: 40 additions & 33 deletions src/lib/updating-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,15 @@ export abstract class UpdatingElement extends HTMLElement {
*/
static get observedAttributes() {
// note: piggy backing on this to ensure we're finalized.
this.finalize();
(this as typeof UpdatingElement).finalize();
const attributes: string[] = [];
// Use forEach so this works even if for/of loops are compiled to for loops
// expecting arrays
this._classProperties!.forEach((v, p) => {
const attr = this._attributeNameForProperty(p, v);
(this as typeof UpdatingElement)._classProperties!.forEach((v, p) => {
const attr =
(this as typeof UpdatingElement)._attributeNameForProperty(p, v);
if (attr !== undefined) {
this._attributeToPropertyMap.set(attr, p);
(this as typeof UpdatingElement)._attributeToPropertyMap.set(attr, p);
attributes.push(attr);
}
});
Expand All @@ -255,16 +256,18 @@ export abstract class UpdatingElement extends HTMLElement {
/** @nocollapse */
private static _ensureClassProperties() {
// ensure private storage for property declarations.
if (!this.hasOwnProperty(
JSCompiler_renameProperty('_classProperties', this))) {
this._classProperties = new Map();
if (!(this as typeof UpdatingElement)
.hasOwnProperty(JSCompiler_renameProperty(
'_classProperties', (this as typeof UpdatingElement)))) {
(this as typeof UpdatingElement)._classProperties = new Map();
// NOTE: Workaround IE11 not supporting Map constructor argument.
const superProperties: PropertyDeclarationMap =
Object.getPrototypeOf(this)._classProperties;
Object.getPrototypeOf((this as typeof UpdatingElement))
._classProperties;
if (superProperties !== undefined) {
superProperties.forEach(
(v: PropertyDeclaration, k: PropertyKey) =>
this._classProperties!.set(k, v));
(this as typeof UpdatingElement)._classProperties!.set(k, v));
}
}
}
Expand All @@ -282,28 +285,27 @@ export abstract class UpdatingElement extends HTMLElement {
// Note, since this can be called by the `@property` decorator which
// is called before `finalize`, we ensure storage exists for property
// metadata.
this._ensureClassProperties();
this._classProperties!.set(name, options);
(this as typeof UpdatingElement)._ensureClassProperties();
(this as typeof UpdatingElement)._classProperties!.set(name, options);
// Do not generate an accessor if the prototype already has one, since
// it would be lost otherwise and that would never be the user's intention;
// Instead, we expect users to call `requestUpdate` themselves from
// user-defined accessors. Note that if the super has an accessor we will
// still overwrite it
if (options.noAccessor || this.prototype.hasOwnProperty(name)) {
if (options.noAccessor ||
(this as typeof UpdatingElement).prototype.hasOwnProperty(name)) {
return;
}
const key = typeof name === 'symbol' ? Symbol() : `__${name}`;
Object.defineProperty(this.prototype, name, {
Object.defineProperty((this as typeof UpdatingElement).prototype, name, {
// tslint:disable-next-line:no-any no symbol in index
get(): any {
return this[key];
return (this as {[key: string]: unknown})[key as string];
},
set(this: UpdatingElement, value: unknown) {
// tslint:disable-next-line:no-any no symbol in index
const oldValue = (this as any)[name];
// tslint:disable-next-line:no-any no symbol in index
(this as any)[key] = value;
this._requestUpdate(name, oldValue);
const oldValue = (this as {[key: string]: unknown})[name as string];
(this as {[key: string]: unknown})[key as string] = value;
(this as UpdatingElement)._requestUpdate(name, oldValue);
},
configurable: true,
enumerable: true
Expand All @@ -316,25 +318,29 @@ export abstract class UpdatingElement extends HTMLElement {
* @nocollapse
*/
protected static finalize() {
if (this.hasOwnProperty(JSCompiler_renameProperty('finalized', this)) &&
this.finalized) {
if ((this as typeof UpdatingElement)
.hasOwnProperty(JSCompiler_renameProperty(
'finalized', (this as typeof UpdatingElement))) &&
(this as typeof UpdatingElement).finalized) {
return;
}
// finalize any superclasses
const superCtor = Object.getPrototypeOf(this);
const superCtor = Object.getPrototypeOf((this as typeof UpdatingElement));
if (typeof superCtor.finalize === 'function') {
superCtor.finalize();
}
this.finalized = true;
this._ensureClassProperties();
(this as typeof UpdatingElement).finalized = true;
(this as typeof UpdatingElement)._ensureClassProperties();
// initialize Map populated in observedAttributes
this._attributeToPropertyMap = new Map();
(this as typeof UpdatingElement)._attributeToPropertyMap = new Map();
// make any properties
// Note, only process "own" properties since this element will inherit
// any properties defined on the superClass, and finalization ensures
// the entire prototype chain is finalized.
if (this.hasOwnProperty(JSCompiler_renameProperty('properties', this))) {
const props = this.properties;
if ((this as typeof UpdatingElement)
.hasOwnProperty(JSCompiler_renameProperty(
'properties', (this as typeof UpdatingElement)))) {
const props = (this as typeof UpdatingElement).properties;
// support symbols in properties (IE11 does not support this)
const propKeys = [
...Object.getOwnPropertyNames(props),
Expand All @@ -347,7 +353,7 @@ export abstract class UpdatingElement extends HTMLElement {
// note, use of `any` is due to TypeSript lack of support for symbol in
// index types
// tslint:disable-next-line:no-any no symbol in index
this.createProperty(p, (props as any)[p]);
(this as typeof UpdatingElement).createProperty(p, (props as any)[p]);
}
}
}
Expand Down Expand Up @@ -441,7 +447,8 @@ export abstract class UpdatingElement extends HTMLElement {
*/
protected initialize() {
this._saveInstanceProperties();
// ensures first update will be caught by an early access of `updateComplete`
// ensures first update will be caught by an early access of
// `updateComplete`
this._requestUpdate();
}

Expand Down Expand Up @@ -486,10 +493,10 @@ export abstract class UpdatingElement extends HTMLElement {

connectedCallback() {
this._updateState = this._updateState | STATE_HAS_CONNECTED;
// Ensure first connection completes an update. Updates cannot complete before
// connection and if one is pending connection the `_hasConnectionResolver`
// will exist. If so, resolve it to complete the update, otherwise
// requestUpdate.
// Ensure first connection completes an update. Updates cannot complete
// before connection and if one is pending connection the
// `_hasConnectionResolver` will exist. If so, resolve it to complete the
// update, otherwise requestUpdate.
if (this._hasConnectedResolver) {
this._hasConnectedResolver();
this._hasConnectedResolver = undefined;
Expand Down
12 changes: 7 additions & 5 deletions src/lit-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ export class LitElement extends UpdatingElement {
super.finalize();
// Prepare styling that is stamped at first render time. Styling
// is built from user provided `styles` or is inherited from the superclass.
this._styles =
this.hasOwnProperty(JSCompiler_renameProperty('styles', this)) ?
this._getUniqueStyles() :
this._styles || [];
(this as typeof LitElement)._styles =
(this as typeof LitElement)
.hasOwnProperty(JSCompiler_renameProperty(
'styles', (this as typeof LitElement))) ?
(this as typeof LitElement)._getUniqueStyles() :
(this as typeof LitElement)._styles || [];
}

/** @nocollapse */
Expand All @@ -102,7 +104,7 @@ export class LitElement extends UpdatingElement {
// shared styles will generate new stylesheet objects, which is wasteful.
// This should be addressed when a browser ships constructable
// stylesheets.
const userStyles = this.styles;
const userStyles = (this as typeof LitElement).styles;
const styles: CSSResult[] = [];
if (Array.isArray(userStyles)) {
const flatStyles = flattenStyles(userStyles);
Expand Down
Loading

0 comments on commit f552fc4

Please sign in to comment.