Skip to content
This repository has been archived by the owner on Oct 24, 2021. It is now read-only.

Commit

Permalink
Support object index signatures.
Browse files Browse the repository at this point in the history
Closure `Object<foo, bar>` maps to TypeScript `{[key: foo]: bar}`.
  • Loading branch information
aomarks committed Dec 18, 2017
1 parent c309bb0 commit 41f09d0
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<foo, bar>` maps to `{[key: foo]: bar}`).

## [0.3.1] - 2017-12-15
- Convert Closure `Object` to TypeScript `object`.
Expand Down
26 changes: 24 additions & 2 deletions src/closure-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
* rights grant found at http://polymer.github.io/PATENTS.txt
*/

// TODO Object<foo>, Object<foo, bar>
//
// 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
Expand Down Expand Up @@ -118,6 +116,8 @@ function convert(node: doctrine.Type, templateTypes: string[]): ts.Type {

if (isParameterizedArray(node)) { // Array<foo>
t = convertArray(node, templateTypes);
} else if (isParameterizedObject(node)) { // Object<foo, bar>
t = convertIndexableObject(node, templateTypes);
} else if (isUnion(node)) { // foo|bar
t = convertUnion(node, templateTypes);
} else if (isFunction(node)) { // function(foo): bar
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -264,6 +276,16 @@ function isBareArray(node: doctrine.Type):
return node.type === 'NameExpression' && node.name === 'Array';
}

/**
* Matches `Object<foo, bar>` 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';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/test/goldens/polymer/lib/mixins/element-mixin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ declare namespace Polymer {
rootPath: string;
importPath: string;
root: StampedTemplate|HTMLElement|ShadowRoot|null;
$: any;
$: {[key: string]: Element};

/**
* Stamps the element template.
Expand Down
23 changes: 22 additions & 1 deletion src/ts-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Node> {
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');
Expand Down

0 comments on commit 41f09d0

Please sign in to comment.