Skip to content

Adding plumbing to support more granular and maintainable typings #870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
},
"scripts": {
"build_all": "npm run build_es6 && npm run build_amd && npm run build_cjs && npm run build_global && npm run generate_packages",
"build_amd": "rm -rf dist/amd && tsc typings/es6-shim/es6-shim.d.ts src/Rx.ts -m amd --outDir dist/amd --sourcemap --target ES5 --diagnostics --pretty",
"build_cjs": "rm -rf dist/cjs && tsc typings/es6-shim/es6-shim.d.ts src/Rx.ts src/Rx.KitchenSink.ts -m commonjs --outDir dist/cjs --sourcemap --target ES5 -d --diagnostics --pretty",
"build_es6": "rm -rf dist/es6 && tsc src/Rx.ts src/Rx.KitchenSink.ts --outDir dist/es6 --sourceMap --target ES6 -d --diagnostics --pretty",
"build_amd": "npm run build_operators && rm -rf dist/amd && tsc typings/es6-shim/es6-shim.d.ts src/Rx.ts -m amd --outDir dist/amd --sourcemap --target ES5 --diagnostics --pretty",
"build_cjs": "npm run build_operators && rm -rf dist/cjs && tsc typings/es6-shim/es6-shim.d.ts src/Rx.ts src/Rx.KitchenSink.ts -m commonjs --outDir dist/cjs --sourcemap --target ES5 -d --diagnostics --pretty",
"build_es6": "npm run build_operators && rm -rf dist/es6 && tsc src/Rx.ts src/Rx.KitchenSink.ts --outDir dist/es6 --sourceMap --target ES6 -d --diagnostics --pretty",
"build_closure": "java -jar ./node_modules/google-closure-compiler/compiler.jar ./dist/global/Rx.js --language_in ECMASCRIPT5 --create_source_map ./dist/global/Rx.min.js.map --js_output_file ./dist/global/Rx.min.js",
"build_global": "rm -rf dist/global && mkdir \"dist/global\" && browserify src/Rx.global.js --outfile dist/global/Rx.js && npm run build_closure",
"build_global": "npm run build_operators && rm -rf dist/global && mkdir \"dist/global\" && browserify src/Rx.global.js --outfile dist/global/Rx.js && npm run build_closure",
"build_perf": "npm run build_cjs && npm run build_global && webdriver-manager update && npm run perf",
"build_test": "rm -rf dist/ && npm run lint && npm run build_cjs && jasmine",
"build_cover": "rm -rf dist/ && npm run lint && npm run build_cjs && npm run cover",
Expand All @@ -37,7 +37,8 @@
"generate_packages": "node .make-packages.js",
"generate_operator_patches": "tsc -outDir dist/ ./tools/generate-operator-patches.ts && node ./dist/generate-operator-patches.js --exec",
"commit": "git-cz",
"check_circular_dependencies": "madge ./dist/cjs --circular"
"check_circular_dependencies": "madge ./dist/cjs --circular",
"build_operators": "node typingsgen.js"
},
"repository": {
"type": "git",
Expand Down
3 changes: 3 additions & 0 deletions src/CoreOperators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {ConnectableObservable} from './observable/ConnectableObservable';
import {Subject} from './Subject';
import {GroupedObservable} from './operator/groupBy-support';
import {Notification} from './Notification';
/* tslint:disable */
import * as operator from './operator-typings';
/* tslint:enable */

export interface CoreOperators<T> {
buffer?: (closingNotifier: Observable<any>) => Observable<T[]>;
Expand Down
68 changes: 41 additions & 27 deletions src/Observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ import {Subject} from './Subject';
import {Notification} from './Notification';
import {rxSubscriber} from'./symbol/rxSubscriber';

/* tslint:disable */
import * as operator from './operator-typings';
/* tslint:enable */
import {combineLatest as combineLatestStatic} from './operator/combineLatest-static';
import {concat as concatStatic} from './operator/concat-static';
import {merge as mergeStatic} from './operator/merge-static';
import {zip as zipStatic} from './operator/zip-static';
import {BoundCallbackObservable} from './observable/bindCallback';
import {DeferObservable} from './observable/defer';
import {EmptyObservable} from './observable/empty';
import {ForkJoinObservable} from './observable/forkJoin';
import {FromObservable} from './observable/from';
import {ArrayObservable} from './observable/fromArray';
import {FromEventObservable} from './observable/fromEvent';
import {FromEventPatternObservable} from './observable/fromEventPattern';
import {PromiseObservable} from './observable/fromPromise';
import {IntervalObservable} from './observable/interval';
import {TimerObservable} from './observable/timer';
import {RangeObservable} from './observable/range';
import {InfiniteObservable} from './observable/never';
import {ErrorObservable} from './observable/throw';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is going to introduce circular dependencies. If you rebase, you can run npm run check_circular_dependencies and see.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They'll get thrown away, because they're just used strictly for the types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you able to share how this will be removed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when it's transpiled to JS, it'll strip anything that's only used for type definitions (but retained in the .d.ts file)

import {Foo} from './foo'

function doStuff(foo: Foo){}

=>

function doStuff(foo){}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I'm feeling this specific changes (import types then assign static method with bring it via typeof) is orthogonal and can be separated - is my understanding correct? (at least just taking this changes locally into TOT seems working)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, those changes by themselves don't really depend on everything else

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about separate this change into another PR? this change's quite straightforward and small, can be checked easily. Also it'll reduce this PR's scope.


/**
* A representation of any set of values over any amount of time. This the most basic building block
* of RxJS.
Expand Down Expand Up @@ -156,33 +178,25 @@ export class Observable<T> implements CoreOperators<T> {
}

// static method stubs
static bindCallback: <T>(callbackFunc: Function, selector?: Function, scheduler?: Scheduler) => Function;
static combineLatest: <T>(...observables: Array<Observable<any> |
Array<Observable<any>> |
((...values: Array<any>) => T) |
Scheduler>) => Observable<T>;
static concat: <T>(...observables: Array<Observable<any> | Scheduler>) => Observable<T>;
static defer: <T>(observableFactory: () => Observable<T>) => Observable<T>;
static empty: <T>(scheduler?: Scheduler) => Observable<T>;
static forkJoin: (...sources: Array<Observable<any> |
Array<Observable<any>> |
Promise<any> |
((...values: Array<any>) => any)>) => Observable<any>;
static from: <T>(iterable: any, scheduler?: Scheduler) => Observable<T>;
static fromArray: <T>(array: T[], scheduler?: Scheduler) => Observable<T>;
static fromEvent: <T>(element: any, eventName: string, selector?: (...args: Array<any>) => T) => Observable<T>;
static fromEventPattern: <T>(addHandler: (handler: Function) => void,
removeHandler: (handler: Function) => void,
selector?: (...args: Array<any>) => T) => Observable<T>;
static fromPromise: <T>(promise: Promise<T>, scheduler?: Scheduler) => Observable<T>;
static interval: (interval: number, scheduler?: Scheduler) => Observable<number>;
static merge: <T>(...observables: Array<Observable<any> | Scheduler | number>) => Observable<T>;
static never: <T>() => Observable<T>;
static of: <T>(...values: Array<T | Scheduler>) => Observable<T>;
static range: (start: number, end: number, scheduler?: Scheduler) => Observable<number>;
static throw: <T>(error: T) => Observable<T>;
static timer: (dueTime?: number | Date, period?: number | Scheduler, scheduler?: Scheduler) => Observable<number>;
static zip: <T>(...observables: Array<Observable<any> | ((...values: Array<any>) => T)>) => Observable<T>;
static bindCallback: typeof BoundCallbackObservable.create;
static combineLatest: typeof combineLatestStatic;
static concat: typeof concatStatic;
static defer: typeof DeferObservable.create;
static empty: typeof EmptyObservable.create;
static forkJoin: typeof ForkJoinObservable.create;
static from: typeof FromObservable.create;
static fromArray: typeof ArrayObservable.create;
static fromEvent: typeof FromEventObservable.create;
static fromEventPattern: typeof FromEventPatternObservable.create;
static fromPromise: typeof PromiseObservable.create;
static interval: typeof IntervalObservable.create;
static merge: typeof mergeStatic;
static never: typeof InfiniteObservable.create;
static of: typeof ArrayObservable.of;
static range: typeof RangeObservable.create;
static throw: typeof ErrorObservable.create;
static timer: typeof TimerObservable.create;
static zip: typeof zipStatic;

// core operators
buffer: (closingNotifier: Observable<any>) => Observable<T[]>;
Expand Down
14 changes: 14 additions & 0 deletions src/operator-typings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* tslint:disable:class-name */ /* tslint:disable:no-unused-variable */ /* tslint:disable:max-line-length */
import {Observable} from './Observable';
import {ConnectableObservable} from './observable/ConnectableObservable';
import {Scheduler} from './Scheduler';
import {Notification} from './Notification';
import {Subject} from './Subject';
import {Observer} from './Observer';
import {GroupedObservable} from './operator/groupBy-support';
import {GroupByObservable} from './operator/groupBy';
import {TimeInterval} from './operator/extended/timeInterval';
import {ObservableInput, ObservableOrPromise, ArrayOrIterator, _Selector, _IndexSelector, _SwitchMapResultSelector, _ObservableMergeMapProjector, _IteratorMergeMapProjector, _Predicate, _PredicateObservable, _Comparer, _Accumulator, _MergeAccumulator} from './types';

/* ||| MARKER ||| */
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of my rebase I also made a tweak here, any generate-rated interfaces will be but between the two markers, allowing maintenance to happen directly in the operator-typings.ts file, if we need to add in a new import for example.

/* ||| MARKER ||| */
17 changes: 17 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Observable} from './Observable';
export type ObservableOrPromise<T> = Observable<T> | Promise<T>;
export type ArrayOrIterator<T> = Iterator<T> | ArrayLike<T> | Array<T>;
export type ObservableInput<T> = Observable<T> | Promise<T> | Iterator<T> | ArrayLike<T>;

export type _Selector<T, TResult> = (value: T) => TResult;
export type _IndexSelector<T, TResult> = (value: T, index: number) => TResult;
export type _SwitchMapResultSelector<T1, T2, TResult> = (outerValue: T1, innerValue: T2, outerIndex: number, innerIndex: number) => TResult;
export type _ObservableMergeMapProjector<T, R> = (value: T, index: number) => ObservableOrPromise<R>;
export type _IteratorMergeMapProjector<T, R> = (value: T, index: number) => ArrayOrIterator<R>;

export type _Predicate<T> = _Selector<T, boolean>;
export type _PredicateObservable<T> = (value: T, index: number, observable: Observable<T>) => boolean;

export type _Comparer<T, TResult> = (value1: T, value2: T) => TResult;
export type _Accumulator<T, TAcc> = (acc: TAcc, value: T) => TAcc;
export type _MergeAccumulator<T, TAcc> = (acc: TAcc, value: T) => Observable<TAcc>;
9 changes: 5 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
"indentSize": 2,
"tabSize": 2
},
"files": [
"src/Rx.ts",
"src/Rx.KitchenSink.ts"
]
"files": [
"typings/es6-build-shim.d.ts",
"src/Rx.ts",
"src/Rx.KitchenSink.ts"
]
}
1 change: 1 addition & 0 deletions typings/es6-build-shim.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare type IterableShim<T> = Iterable<T>;
118 changes: 118 additions & 0 deletions typingsgen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
var fs = require('fs');
var regex = /export interface .*?Operators<T> \{([\S|\s]*)\}/;

var core = fs.readFileSync('./src/CoreOperators.ts').toString();
var kitchenSink = fs.readFileSync('./src/Rx.KitchenSink.ts').toString();
var combinedMethods = core.match(regex)[1].trim() + '\n' + kitchenSink.match(regex)[1].trim();
var contents = combinedMethods.split('\n');

var operators = {};
var fileResult = '';

for (var i = 0; i < contents.length; i++) {
var item = contents[i].trim();
if (item) {
var file = item.match(/(.*?)\: operator.operator_proto_(.*?)<T>;/);
if (!file) {
continue;
}

var filename = file[2].trim();
var fileContent;

if (fs.existsSync('./src/operator/' + filename + '.ts')) {
fileContent = fs.readFileSync('./src/operator/' + filename + '.ts').toString('utf8');
} else {
fileContent = fs.readFileSync('./src/operator/extended/' + filename + '.ts').toString('utf8');
}

fileContent = computeTypingsFor(fileContent);

var methods = [];

var r = new RegExp('export function [_]?' + filename + '([\\s|\\S]*?[\\;\\{])', 'g');

do {
var result = r.exec(fileContent);
if (result) {
var method = result[1].trim();
if (methods.length > 0 && method.indexOf('{') > -1) {
continue;
}

method = method.split(/\n/g)
.filter(function (x) { return !!x; })
.map(function (x) { return ('' + x).trim(); })
.join(' ')
.replace(/ = .*?([\,|\)])/g, '$1');

if (method[method.length - 1] === ';' || method[method.length - 1] === '{') {
method = method.substr(0, method.length - 1).trim();
}

method = method.replace(/^<T>/, '').replace(/^<T, /, '<');
methods.push(method);
}
} while(result);

if (!operators[filename]) {
operators[filename] = true;
fileResult += 'export interface operator_proto_' + filename + '<T> {\n ' + methods.join(';\n ') + ';\n}\n';
}
}
}

var typingsContent = fs.readFileSync('./src/operator-typings.ts').toString();
fileResult = '/* ||| MARKER ||| */\n' + fileResult + '/* ||| MARKER ||| */';
typingsContent = typingsContent.replace(/(\/\* \|\|\| MARKER \|\|\| \*\/[\s|\S]*?\/\* \|\|\| MARKER \|\|\| \*\/)/, fileResult);
fs.writeFileSync('./src/operator-typings.ts', typingsContent);


function computeTypingsFor(s) {
var captureRegex = /\/\*\-\-([\s|\S]*?)-\-\*\//g;
var computeNumberRegex = /\*compute (\d.*?)?\*/;
var tokenRegex = /\{.*?\}/g;

s = s.replace(captureRegex, function(capture) {
capture = capture.trim();
capture = capture.substr(3, capture.length - 3 * 2);
var compute = computeNumberRegex.exec(capture);
if (compute) {
compute = compute[1] || '6';
} else {
compute = '6';
}
var range = compute.split('-');
if (range.length === 1) {
var start = 1;
var end = range[0];
} else {
var start = range[0];
var end = range[1];
}

capture = capture.replace(computeNumberRegex, '').trim();

var tokenResult;
var results = [];
for (var number = start; number <= end; number++) {
var res = capture.replace(tokenRegex, function(capture, index, str) {
var items = [];
capture = capture.substr(1, capture.length - 2);
for (var i = start; i <= number; i++) {
var typeName = 'T' + (i === 1 ? '' : i);
items.push(capture.replace(/\|X\|/g, typeName).replace(/\|v\|/g, 'v' + i));
}

return items.join(', ');
});
results.push(res);
}

return results.join('\n');
});

return s;
}