From 6dc8857592ac6c2ae711edd1d9fe505f2b8b4ca4 Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Thu, 17 Jan 2019 15:07:01 -0800 Subject: [PATCH 1/3] Remove almost all uses of `any`. Also marks any properties that seem readonly as `readonly`. Likewise `ReadonlyArray`. --- src/lib/decorators.ts | 10 ++++-- src/lib/updating-element.ts | 47 ++++++++++++++++----------- src/lit-element.ts | 2 +- src/test/lib/decorators_test.ts | 2 ++ src/test/lib/updating-element_test.ts | 2 ++ src/test/lit-element_styling_test.ts | 1 + src/test/lit-element_test.ts | 2 ++ tsconfig.json | 2 ++ tslint.json | 1 + 9 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/lib/decorators.ts b/src/lib/decorators.ts index bc72e65e..f5a2eb36 100644 --- a/src/lib/decorators.ts +++ b/src/lib/decorators.ts @@ -18,7 +18,7 @@ import {LitElement} from '../lit-element.js'; import {PropertyDeclaration, UpdatingElement} from './updating-element.js'; export type Constructor = { - new (...args: any[]): T + new (...args: unknown[]): T }; // From the TC39 Decorators proposal @@ -47,6 +47,7 @@ const legacyCustomElement = // `Constructor` for some reason. // `Constructor` is helpful to make sure the decorator is // applied to elements however. + // tslint:disable-next-line:no-any return clazz as any; }; @@ -106,6 +107,7 @@ const standardProperty = // initializer: descriptor.initializer, // } // ], + // tslint:disable-next-line:no-any decorator initializer(this: any) { if (typeof element.initializer === 'function') { this[element.key] = element.initializer!.call(this); @@ -132,6 +134,7 @@ const legacyProperty = * @ExportDecoratedItems */ export function property(options?: PropertyDeclaration) { + // tslint:disable-next-line:no-any decorator return (protoOrDescriptor: Object|ClassElement, name?: PropertyKey): any => (name !== undefined) ? legacyProperty(options!, protoOrDescriptor as Object, name) : @@ -177,6 +180,7 @@ const standardQuery = (descriptor: PropertyDescriptor, element: ClassElement) => function _query(queryFn: (target: NodeSelector, selector: string) => T) { return (selector: string) => (protoOrDescriptor: Object|ClassElement, + // tslint:disable-next-line:no-any decorator name?: PropertyKey): any => { const descriptor = { get(this: LitElement) { @@ -203,6 +207,7 @@ const standardEventOptions = }; const legacyEventOptions = + // tslint:disable-next-line:no-any legacy decorator (options: AddEventListenerOptions, proto: any, name: PropertyKey) => { Object.assign(proto[name], options); }; @@ -243,4 +248,5 @@ export const eventOptions = (options: AddEventListenerOptions) => (name !== undefined) ? legacyEventOptions(options, protoOrDescriptor as Object, name) : standardEventOptions(options, protoOrDescriptor as ClassElement)) as - any; + // tslint:disable-next-line:no-any decorator + any; diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index f753dc08..1ab3bf7e 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -33,7 +33,7 @@ declare global { /** * Converts property values to and from attribute values. */ -export interface ComplexAttributeConverter { +export interface ComplexAttributeConverter { /** * Function called to convert an attribute value to a property * value. @@ -47,13 +47,13 @@ export interface ComplexAttributeConverter { toAttribute?(value: Type, type?: TypeHint): string|null; } -type AttributeConverter = +type AttributeConverter = ComplexAttributeConverter|((value: string, type?: TypeHint) => Type); /** * Defines options for a property accessor. */ -export interface PropertyDeclaration { +export interface PropertyDeclaration { /** * Indicates how and whether the property becomes an observed attribute. * If the value is `false`, the property is not added to `observedAttributes`. @@ -61,14 +61,14 @@ export interface PropertyDeclaration { * becomes `foobar`). If a string, the string value is observed (e.g * `attribute: 'foo-bar'`). */ - attribute?: boolean|string; + readonly attribute?: boolean|string; /** * Indicates the type of the property. This is used only as a hint for the * `converter` to determine how to convert the attribute * to/from a property. */ - type?: TypeHint; + readonly type?: TypeHint; /** * Indicates how to convert the attribute to/from a property. If this value @@ -82,7 +82,7 @@ export interface PropertyDeclaration { * the property is never updated again as a result of the attribute changing, * and vice versa. */ - converter?: AttributeConverter; + readonly converter?: AttributeConverter; /** * Indicates if the property should reflect to an attribute. @@ -91,7 +91,7 @@ export interface PropertyDeclaration { * property option and the value of the property converted using the rules * from the `converter` property option. */ - reflect?: boolean; + readonly reflect?: boolean; /** * A function that indicates if a property should be considered changed when @@ -108,7 +108,7 @@ export interface PropertyDeclaration { * `this.requestUpdate(propertyName, oldValue)` to request an update when * the property changes. */ - noAccessor?: boolean; + readonly noAccessor?: boolean; } /** @@ -117,7 +117,7 @@ export interface PropertyDeclaration { * PropertyDeclaration options. */ export interface PropertyDeclarations { - [key: string]: PropertyDeclaration; + readonly [key: string]: PropertyDeclaration; } type PropertyDeclarationMap = Map; @@ -128,7 +128,7 @@ export type PropertyValues = Map; export const defaultConverter: ComplexAttributeConverter = { - toAttribute(value: any, type?: any) { + toAttribute(value: unknown, type?: unknown): string | null { switch (type) { case Boolean: return value ? '' : null; @@ -136,12 +136,12 @@ export const defaultConverter: ComplexAttributeConverter = { case Array: // if the value is `null` or `undefined` pass this through // to allow removing/no change behavior. - return value == null ? value : JSON.stringify(value); + return value == null ? (value as null) : JSON.stringify(value); } - return value; + return value as string | null; }, - fromAttribute(value: any, type?: any) { + fromAttribute(value: unknown, type?: unknown) { switch (type) { case Boolean: return value !== null; @@ -149,7 +149,7 @@ export const defaultConverter: ComplexAttributeConverter = { return value === null ? null : Number(value); case Object: case Array: - return JSON.parse(value); + return JSON.parse(value as string); } return value; } @@ -256,10 +256,12 @@ export abstract class UpdatingElement extends HTMLElement { JSCompiler_renameProperty('_classProperties', this))) { this._classProperties = new Map(); // NOTE: Workaround IE11 not supporting Map constructor argument. - const superProperties = Object.getPrototypeOf(this)._classProperties; + const superProperties: PropertyDeclarationMap = + Object.getPrototypeOf(this)._classProperties; if (superProperties !== undefined) { superProperties.forEach( - (v: any, k: PropertyKey) => this._classProperties!.set(k, v)); + (v: PropertyDeclaration, k: PropertyKey) => + this._classProperties!.set(k, v)); } } } @@ -289,11 +291,15 @@ export abstract class UpdatingElement extends HTMLElement { } const key = typeof name === 'symbol' ? Symbol() : `__${name}`; Object.defineProperty(this.prototype, name, { + // tslint:disable-next-line:no-any no symbol in index get(): any { + // tslint:disable-next-line:no-any no symbol in index return (this as any)[key]; }, - set(this: UpdatingElement, value: any) { + 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); }, @@ -338,6 +344,7 @@ export abstract class UpdatingElement extends HTMLElement { for (const p of propKeys) { // 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]); } } @@ -468,6 +475,7 @@ export abstract class UpdatingElement extends HTMLElement { private _applyInstanceProperties() { // Use forEach so this works even if for/of loops are compiled to for loops // expecting arrays + // tslint:disable-next-line:no-any this._instanceProperties!.forEach((v, p) => (this as any)[p] = v); this._instanceProperties = undefined; } @@ -547,7 +555,8 @@ export abstract class UpdatingElement extends HTMLElement { // mark state reflecting this._updateState = this._updateState | STATE_IS_REFLECTING_TO_PROPERTY; this[propName as keyof this] = - ctor._propertyValueFromAttribute(value, options); + // tslint:disable-next-line:no-any + ctor._propertyValueFromAttribute(value, options) as any; // mark state not reflecting this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_PROPERTY; } @@ -566,7 +575,7 @@ export abstract class UpdatingElement extends HTMLElement { * @param oldValue {any} (optional) old value of requesting property * @returns {Promise} A Promise that is resolved when the update completes. */ - requestUpdate(name?: PropertyKey, oldValue?: any) { + requestUpdate(name?: PropertyKey, oldValue?: unknown) { let shouldRequestUpdate = true; // if we have a property key, perform property update steps. if (name !== undefined && !this._changedProperties.has(name)) { diff --git a/src/lit-element.ts b/src/lit-element.ts index 558ec603..e2bbada9 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -208,7 +208,7 @@ export class LitElement extends UpdatingElement { */ protected update(changedProperties: PropertyValues) { super.update(changedProperties); - const templateResult = this.render() as any; + const templateResult = this.render() as unknown; if (templateResult instanceof TemplateResult) { (this.constructor as typeof LitElement) .render( diff --git a/src/test/lib/decorators_test.ts b/src/test/lib/decorators_test.ts index c63b7a2f..9ecbfcff 100644 --- a/src/test/lib/decorators_test.ts +++ b/src/test/lib/decorators_test.ts @@ -16,6 +16,8 @@ import {eventOptions, property} from '../../lib/decorators.js'; import {customElement, html, LitElement, PropertyValues, query, queryAll} from '../../lit-element.js'; import {generateElementName} from '../test-helpers.js'; +// tslint:disable:no-any ok in tests + let hasOptions; const supportsOptions = (function() { if (hasOptions !== undefined) { diff --git a/src/test/lib/updating-element_test.ts b/src/test/lib/updating-element_test.ts index ebcea360..8daa277f 100644 --- a/src/test/lib/updating-element_test.ts +++ b/src/test/lib/updating-element_test.ts @@ -16,6 +16,8 @@ import {property} from '../../lib/decorators.js'; import {ComplexAttributeConverter, PropertyDeclarations, PropertyValues, UpdatingElement} from '../../lib/updating-element.js'; import {generateElementName} from '../test-helpers.js'; +// tslint:disable:no-any ok in tests + const assert = chai.assert; suite('UpdatingElement', () => { diff --git a/src/test/lit-element_styling_test.ts b/src/test/lit-element_styling_test.ts index 1a34a58d..819db6e4 100644 --- a/src/test/lit-element_styling_test.ts +++ b/src/test/lit-element_styling_test.ts @@ -456,6 +456,7 @@ suite('Static get styles', () => { test('`css` get styles throws when unsafe values are used', async () => { assert.throws(() => { + // tslint:disable:no-any intentionally unsafe code css`div { border: ${`2px solid blue;` as any}}`; }); }); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 89b9dc2e..72c18b5c 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -18,6 +18,8 @@ import {generateElementName, stripExpressionDelimeters} from './test-helpers.js' const assert = chai.assert; +// tslint:disable:no-any ok in tests + suite('LitElement', () => { let container: HTMLElement; diff --git a/tsconfig.json b/tsconfig.json index 1aa2cc75..b904df43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,8 @@ "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, "outDir": "./", // Only necessary because @types/uglify-js can't find types for source-map "skipLibCheck": true, diff --git a/tslint.json b/tslint.json index 54984fb6..7b4fc2a9 100644 --- a/tslint.json +++ b/tslint.json @@ -7,6 +7,7 @@ "spaces" ], "prefer-const": true, + "no-any": true, "no-duplicate-variable": true, "no-eval": true, "no-internal-module": true, From 9231e088ab8d8ff57c65e76365b73c2ad8f4b84d Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Thu, 17 Jan 2019 15:20:47 -0800 Subject: [PATCH 2/3] attributeChangedCallback can get null values for removed attributes. --- src/lib/updating-element.ts | 51 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 1ab3bf7e..67ca86cb 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -38,13 +38,16 @@ export interface ComplexAttributeConverter { * Function called to convert an attribute value to a property * value. */ - fromAttribute?(value: string, type?: TypeHint): Type; + fromAttribute?(value: string|null, type?: TypeHint): Type; /** * Function called to convert a property value to an attribute * value. + * + * It returns unknown instead of string, to be compatible with + * https://github.com/WICG/trusted-types (and similar efforts). */ - toAttribute?(value: Type, type?: TypeHint): string|null; + toAttribute?(value: Type, type?: TypeHint): unknown; } type AttributeConverter = @@ -128,28 +131,28 @@ export type PropertyValues = Map; export const defaultConverter: ComplexAttributeConverter = { - toAttribute(value: unknown, type?: unknown): string | null { + toAttribute(value: unknown, type?: unknown): unknown { switch (type) { - case Boolean: - return value ? '' : null; - case Object: - case Array: - // if the value is `null` or `undefined` pass this through - // to allow removing/no change behavior. - return value == null ? (value as null) : JSON.stringify(value); + case Boolean: + return value ? '' : null; + case Object: + case Array: + // if the value is `null` or `undefined` pass this through + // to allow removing/no change behavior. + return value == null ? value : JSON.stringify(value); } - return value as string | null; + return value; }, - fromAttribute(value: unknown, type?: unknown) { + fromAttribute(value: string|null, type?: unknown) { switch (type) { - case Boolean: - return value !== null; - case Number: - return value === null ? null : Number(value); - case Object: - case Array: - return JSON.parse(value as string); + case Boolean: + return value !== null; + case Number: + return value === null ? null : Number(value); + case Object: + case Array: + return JSON.parse(value!); } return value; } @@ -381,8 +384,8 @@ export abstract class UpdatingElement extends HTMLElement { * `converter` or `converter.fromAttribute` property option. * @nocollapse */ - private static _propertyValueFromAttribute( - value: string, options: PropertyDeclaration) { + private static _propertyValueFromAttribute(value: string|null, + options: PropertyDeclaration) { const type = options.type; const converter = options.converter || defaultConverter; const fromAttribute = @@ -505,7 +508,7 @@ export abstract class UpdatingElement extends HTMLElement { /** * Synchronizes property values when attributes change. */ - attributeChangedCallback(name: string, old: string, value: string) { + attributeChangedCallback(name: string, old: string|null, value: string|null) { if (old !== value) { this._attributeToProperty(name, value); } @@ -534,14 +537,14 @@ export abstract class UpdatingElement extends HTMLElement { if (attrValue == null) { this.removeAttribute(attr); } else { - this.setAttribute(attr, attrValue); + this.setAttribute(attr, attrValue as string); } // mark state not reflecting this._updateState = this._updateState & ~STATE_IS_REFLECTING_TO_ATTRIBUTE; } } - private _attributeToProperty(name: string, value: string) { + private _attributeToProperty(name: string, value: string|null) { // Use tracking info to avoid deserializing attribute value if it was // just set from a property setter. if (this._updateState & STATE_IS_REFLECTING_TO_ATTRIBUTE) { From a7e4a15c1a95c5943c48fca7b5d2e11d80ba7ec3 Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Thu, 17 Jan 2019 15:20:59 -0800 Subject: [PATCH 3/3] Format. --- src/lib/updating-element.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 67ca86cb..cd8ac8bc 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -133,26 +133,26 @@ export const defaultConverter: ComplexAttributeConverter = { toAttribute(value: unknown, type?: unknown): unknown { switch (type) { - case Boolean: - return value ? '' : null; - case Object: - case Array: - // if the value is `null` or `undefined` pass this through - // to allow removing/no change behavior. - return value == null ? value : JSON.stringify(value); + case Boolean: + return value ? '' : null; + case Object: + case Array: + // if the value is `null` or `undefined` pass this through + // to allow removing/no change behavior. + return value == null ? value : JSON.stringify(value); } return value; }, fromAttribute(value: string|null, type?: unknown) { switch (type) { - case Boolean: - return value !== null; - case Number: - return value === null ? null : Number(value); - case Object: - case Array: - return JSON.parse(value!); + case Boolean: + return value !== null; + case Number: + return value === null ? null : Number(value); + case Object: + case Array: + return JSON.parse(value!); } return value; } @@ -384,8 +384,8 @@ export abstract class UpdatingElement extends HTMLElement { * `converter` or `converter.fromAttribute` property option. * @nocollapse */ - private static _propertyValueFromAttribute(value: string|null, - options: PropertyDeclaration) { + private static _propertyValueFromAttribute( + value: string|null, options: PropertyDeclaration) { const type = options.type; const converter = options.converter || defaultConverter; const fromAttribute =