From 41f09d020ea4c3a3aea9c8a39a37c46d16ee82fd Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Mon, 18 Dec 2017 15:15:52 -0800 Subject: [PATCH] Support object index signatures. Closure `Object` maps to TypeScript `{[key: foo]: bar}`. --- CHANGELOG.md | 1 + src/closure-types.ts | 26 +++++++++++++++++-- .../lib/legacy/legacy-element-mixin.d.ts | 2 +- .../polymer/lib/mixins/element-mixin.d.ts | 2 +- src/ts-ast.ts | 23 +++++++++++++++- 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02cc114..1e5af59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Static methods are now supported on classes, elements, and mixins. - Add `renameTypes` config option, a map of renames to apply to named types that can be configured per-project. - Convert Closure `ITemplateArray` type to TypeScript `TemplateStringsArray`. +- Support object index signatures (e.g. `Object` maps to `{[key: foo]: bar}`). ## [0.3.1] - 2017-12-15 - Convert Closure `Object` to TypeScript `object`. diff --git a/src/closure-types.ts b/src/closure-types.ts index c6c5b12..5793ef4 100644 --- a/src/closure-types.ts +++ b/src/closure-types.ts @@ -9,8 +9,6 @@ * rights grant found at http://polymer.github.io/PATENTS.txt */ -// TODO Object, Object -// // Useful resources for working on this package: // https://eslint.org/doctrine/demo/ // https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System @@ -118,6 +116,8 @@ function convert(node: doctrine.Type, templateTypes: string[]): ts.Type { if (isParameterizedArray(node)) { // Array t = convertArray(node, templateTypes); + } else if (isParameterizedObject(node)) { // Object + t = convertIndexableObject(node, templateTypes); } else if (isUnion(node)) { // foo|bar t = convertUnion(node, templateTypes); } else if (isFunction(node)) { // function(foo): bar @@ -189,6 +189,18 @@ function convertArray( ts.anyType); } +function convertIndexableObject( + node: doctrine.type.TypeApplication, + templateTypes: string[]): ts.IndexableObjectType|ts.NameType { + if (node.applications.length !== 2) { + console.error('Parameterized Object must have two parameters.'); + return ts.anyType; + } + return new ts.IndexableObjectType( + convert(node.applications[0], templateTypes), + convert(node.applications[1], templateTypes)); +} + function convertUnion( node: doctrine.type.UnionType, templateTypes: string[]): ts.Type { return new ts.UnionType( @@ -264,6 +276,16 @@ function isBareArray(node: doctrine.Type): return node.type === 'NameExpression' && node.name === 'Array'; } +/** + * Matches `Object` but not `Object` (which is a NameExpression). + */ +function isParameterizedObject(node: doctrine.Type): + node is doctrine.type.TypeApplication { + return node.type === 'TypeApplication' && + node.expression.type === 'NameExpression' && + node.expression.name === 'Object'; +} + function isUnion(node: doctrine.Type): node is doctrine.type.UnionType { return node.type === 'UnionType'; } diff --git a/src/test/goldens/polymer/lib/legacy/legacy-element-mixin.d.ts b/src/test/goldens/polymer/lib/legacy/legacy-element-mixin.d.ts index 944dea9..ac82f94 100644 --- a/src/test/goldens/polymer/lib/legacy/legacy-element-mixin.d.ts +++ b/src/test/goldens/polymer/lib/legacy/legacy-element-mixin.d.ts @@ -34,7 +34,7 @@ declare namespace Polymer { interface LegacyElementMixin { isAttached: boolean; - _debouncers: any; + _debouncers: {[key: string]: Function|null}; /** * Overrides the default `Polymer.PropertyEffects` implementation to diff --git a/src/test/goldens/polymer/lib/mixins/element-mixin.d.ts b/src/test/goldens/polymer/lib/mixins/element-mixin.d.ts index ec53b91..afcb31e 100644 --- a/src/test/goldens/polymer/lib/mixins/element-mixin.d.ts +++ b/src/test/goldens/polymer/lib/mixins/element-mixin.d.ts @@ -138,7 +138,7 @@ declare namespace Polymer { rootPath: string; importPath: string; root: StampedTemplate|HTMLElement|ShadowRoot|null; - $: any; + $: {[key: string]: Element}; /** * Stamps the element template. diff --git a/src/ts-ast.ts b/src/ts-ast.ts index fe1dade..c40a217 100644 --- a/src/ts-ast.ts +++ b/src/ts-ast.ts @@ -416,7 +416,7 @@ export class Param { // A TypeScript type expression. export type Type = NameType|UnionType|ArrayType|FunctionType|ConstructorType| - RecordType|IntersectionType; + RecordType|IntersectionType|IndexableObjectType; // string, MyClass, null, undefined, any export class NameType { @@ -657,6 +657,27 @@ export class IntersectionType { } } +export class IndexableObjectType { + readonly kind = 'indexableObject'; + keyType: Type; + valueType: Type; + + constructor(keyType: Type, valueType: Type) { + this.keyType = keyType; + this.valueType = valueType; + } + + * traverse(): Iterable { + yield this.keyType; + yield this.valueType; + yield this; + } + + serialize(): string { + return `{[key: ${this.keyType.serialize()}]: ${this.valueType.serialize()}}` + } +} + export const anyType = new NameType('any'); export const nullType = new NameType('null'); export const undefinedType = new NameType('undefined');