[meteor] Make check an assertion function (#41414)

Meteor's check is an assertion function, and Match.test returns a type
predicate. TypeScript 3.7 is expressive enough to capture this. This
lets us reduce duplication in Meteor code (e.g. needing to keep function
parameter types and check calls in sync with each other).
This commit is contained in:
Evan Broder 2020-01-08 13:01:05 -08:00 committed by Armando Aguirre
parent 6aa47fae43
commit 2f995d071e
13 changed files with 128 additions and 48 deletions

View File

@ -2,7 +2,7 @@
// Project: https://github.com/Urigo/angular-meteor
// Definitions by: Peter Grman <https://github.com/pgrm>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -2,7 +2,7 @@
// Project: https://github.com/jagi/meteor-astronomy/
// Definitions by: Igor Golovin <https://github.com/Deadly0>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -2,7 +2,7 @@
// Project: https://github.com/jboulhous/dev
// Definitions by: Robbie Van Gorkom <https://github.com/vangorra>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -2,7 +2,7 @@
// Project: https://github.com/okgrow/meteor-persistent-session
// Definitions by: Robbie Van Gorkom <https://github.com/vangorra>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -2,7 +2,7 @@
// Project: https://github.com/prime-8-consulting/meteor-oauth2/
// Definitions by: Robbie Van Gorkom <https://github.com/vangorra>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -3,7 +3,7 @@
// Definitions by: Robert Van Gorkom <https://github.com/vangorra>
// Matthew Zartman <https://github.com/mrz5018>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -3,7 +3,7 @@
// Definitions by: Robbie Van Gorkom <https://github.com/vangorra>
// Matthew Zartman <https://github.com/mattmm3d>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="meteor" />

View File

@ -2,7 +2,7 @@
// Project: meteor-universe-i18n
// Definitions by: Mathias Scherer <https://github.com/mathewmeconry>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference types="react" />
/// <reference types="node" />

View File

@ -1,24 +1,45 @@
declare module "meteor/check" {
module Match {
var Any: any;
var String: any;
var Integer: any;
var Boolean: any;
var undefined: any;
var Object: any;
interface Matcher<T> { _meteorCheckMatcherBrand: void }
export type Pattern =
typeof String |
typeof Number |
typeof Boolean |
typeof Object |
typeof Function |
(new (...args: any[]) => any) |
undefined | null | string | number | boolean |
[Pattern] |
{[key: string]: Pattern} |
Matcher<any>;
export type PatternMatch<T extends Pattern> =
T extends Matcher<infer U> ? U :
T extends typeof String ? string :
T extends typeof Number ? number :
T extends typeof Boolean ? boolean :
T extends typeof Object ? object :
T extends typeof Function ? Function :
T extends undefined | null | string | number | boolean ? T :
T extends new (...args: any[]) => infer U ? U :
T extends [Pattern] ? PatternMatch<T[0]>[] :
T extends {[key: string]: Pattern} ? {[K in keyof T]: PatternMatch<T[K]>} :
unknown;
function Maybe(pattern: any): boolean;
var Any: Matcher<any>;
var Integer: Matcher<number>;
function Optional(pattern: any): boolean;
function Maybe<T extends Pattern>(pattern: T): Matcher<PatternMatch<T> | undefined | null>;
function ObjectIncluding(dico: any): boolean;
function Optional<T extends Pattern>(pattern: T): Matcher<PatternMatch<T> | undefined>;
function OneOf(...patterns: any[]): any;
function ObjectIncluding<T extends {[key: string]: Pattern}>(dico: T): Matcher<PatternMatch<T>>;
function Where(condition: any): any;
function OneOf<T extends Pattern>(...patterns: T[]): Matcher<PatternMatch<T>>;
function test(value: any, pattern: any): boolean;
function Where(condition: (val: any) => boolean): Matcher<any>;
function test<T extends Pattern>(value: any, pattern: T): value is PatternMatch<T>;
}
function check(value: any, pattern: any): void;
function check<T extends Match.Pattern>(value: any, pattern: T): asserts value is Match.PatternMatch<T>;
}

View File

@ -1,22 +1,43 @@
declare module Match {
var Any: any;
var String: any;
var Integer: any;
var Boolean: any;
var undefined: any;
var Object: any;
interface Matcher<T> { _meteorCheckMatcherBrand: void }
export type Pattern =
typeof String |
typeof Number |
typeof Boolean |
typeof Object |
typeof Function |
(new (...args: any[]) => any) |
undefined | null | string | number | boolean |
[Pattern] |
{[key: string]: Pattern} |
Matcher<any>;
export type PatternMatch<T extends Pattern> =
T extends Matcher<infer U> ? U :
T extends typeof String ? string :
T extends typeof Number ? number :
T extends typeof Boolean ? boolean :
T extends typeof Object ? object :
T extends typeof Function ? Function :
T extends undefined | null | string | number | boolean ? T :
T extends new (...args: any) => infer U ? U :
T extends [Pattern] ? PatternMatch<T[0]>[] :
T extends {[key: string]: Pattern} ? {[K in keyof T]: PatternMatch<T[K]>} :
unknown;
function Maybe(pattern: any): boolean;
var Any: Matcher<any>;
var Integer: Matcher<number>;
function Optional(pattern: any): boolean;
function Maybe<T extends Pattern>(pattern: T): Matcher<PatternMatch<T> | undefined | null>;
function ObjectIncluding(dico: any): boolean;
function Optional<T extends Pattern>(pattern: T): Matcher<PatternMatch<T> | undefined>;
function OneOf(...patterns: any[]): any;
function ObjectIncluding<T extends {[key: string]: Pattern}>(dico: T): Matcher<PatternMatch<T>>;
function Where(condition: any): any;
function OneOf<T extends Pattern>(...patterns: T[]): Matcher<PatternMatch<T>>;
function test(value: any, pattern: any): boolean;
function Where(condition: (val: any) => boolean): Matcher<any>;
function test<T extends Pattern>(value: any, pattern: T): value is PatternMatch<T>;
}
declare function check(value: any, pattern: any): void;
declare function check<T extends Match.Pattern>(value: any, pattern: T): asserts value is Match.PatternMatch<T>;

View File

@ -13,8 +13,9 @@
// alesn <https://github.com/alesn>
// Per Bergland <https://github.com/perbergland>
// Nicusor Chiciuc <https://github.com/nicu-chiciuc>
// Evan Broder <https://github.com/ebroder>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
// Minimum TypeScript Version: 3.7
/// <reference path="./accounts-base.d.ts" />
/// <reference path="./globals/accounts-base.d.ts" />

View File

@ -66,8 +66,10 @@ Meteor.publish("adminSecretInfo", function () {
return Rooms.find({ admin: this.userId }, { fields: { secretInfo: 1 } });
});
Meteor.publish("roomAndMessages", function (roomId: string) {
Meteor.publish("roomAndMessages", function (roomId: unknown) {
check(roomId, String);
// $ExpectType string
roomId;
return [
Rooms.find({ _id: roomId }, { fields: { secretInfo: 0 } }),
Messages.find({ roomId: roomId })
@ -77,9 +79,11 @@ Meteor.publish("roomAndMessages", function (roomId: string) {
/**
* Also from Publish and Subscribe, Meteor.publish section
*/
Meteor.publish("counts-by-room", function (roomId: string) {
Meteor.publish("counts-by-room", function (roomId: unknown) {
var self = this;
check(roomId, String);
// $ExpectType string
roomId;
var count = 0;
var initializing = true;
var handle = Messages.find({ roomId: roomId }).observeChanges({
@ -136,10 +140,15 @@ Tracker.autorun(function () {
* From Methods, Meteor.methods section
*/
Meteor.methods({
foo: function (arg1: string, arg2: number[]) {
foo: function (arg1: unknown, arg2: unknown) {
check(arg1, String);
check(arg2, [Number]);
// $ExpectType string
arg1;
// $ExpectType number[]
arg2;
var you_want_to_throw_an_error = true;
if (you_want_to_throw_an_error)
throw new Meteor.Error("404", "Can't find my pants");
@ -595,14 +604,16 @@ var body = Template.body;
*/
var Chats = new Mongo.Collection('chats');
Meteor.publish("chats-in-room", function (roomId: string) {
Meteor.publish("chats-in-room", function (roomId: unknown) {
// Make sure roomId is a string, not an arbitrary mongo selector object.
check(roomId, String);
// $ExpectType string
roomId;
return Chats.find({ room: roomId });
});
Meteor.methods({
addChat: function (roomId: string, message: { text: string, timestamp: Date, tags: string }) {
addChat: function (roomId: unknown, message: unknown) {
check(roomId, String);
check(message, {
text: String,
@ -611,7 +622,14 @@ Meteor.methods({
tags: Match.Optional('Test String')
});
// ... do something with the message ...
// $ExpectType string
roomId;
// $ExpectType string
message.text;
// $ExpectType Date
message.timestamp;
// $ExpectType "Test String"
message.tags;
}
});

View File

@ -80,8 +80,10 @@ Meteor.publish("adminSecretInfo", function () {
return Rooms.find({ admin: this.userId }, { fields: { secretInfo: 1 } });
});
Meteor.publish("roomAndMessages", function (roomId: string) {
Meteor.publish("roomAndMessages", function (roomId: unknown) {
check(roomId, String);
// $ExpectType string
roomId;
return [
Rooms.find({ _id: roomId }, { fields: { secretInfo: 0 } }),
Messages.find({ roomId: roomId })
@ -91,9 +93,11 @@ Meteor.publish("roomAndMessages", function (roomId: string) {
/**
* Also from Publish and Subscribe, Meteor.publish section
*/
Meteor.publish("counts-by-room", function (roomId: string) {
Meteor.publish("counts-by-room", function (roomId: unknown) {
var self = this;
check(roomId, String);
// $ExpectType string
roomId;
var count = 0;
var initializing = true;
var handle = Messages.find({ roomId: roomId }).observeChanges({
@ -150,10 +154,15 @@ Tracker.autorun(function () {
* From Methods, Meteor.methods section
*/
Meteor.methods({
foo: function (arg1: string, arg2: number[]) {
foo: function (arg1: unknown, arg2: unknown) {
check(arg1, String);
check(arg2, [Number]);
// $ExpectType string
arg1;
// $ExpectType number[]
arg2;
var you_want_to_throw_an_error = true;
if (you_want_to_throw_an_error)
throw new Meteor.Error("404", "Can't find my pants");
@ -609,14 +618,16 @@ var body = Template.body;
*/
var Chats = new Mongo.Collection('chats');
Meteor.publish("chats-in-room", function (roomId: string) {
Meteor.publish("chats-in-room", function (roomId: unknown) {
// Make sure roomId is a string, not an arbitrary mongo selector object.
check(roomId, String);
// $ExpectType string
roomId;
return Chats.find({ room: roomId });
});
Meteor.methods({
addChat: function (roomId: string, message: { text: string, timestamp: Date, tags: string }) {
addChat: function (roomId: unknown, message: unknown) {
check(roomId, String);
check(message, {
text: String,
@ -625,7 +636,14 @@ Meteor.methods({
tags: Match.Optional('Test String')
});
// ... do something with the message ...
// $ExpectType string
roomId;
// $ExpectType string
message.text;
// $ExpectType Date
message.timestamp;
// $ExpectType "Test String"
message.tags;
}
});
@ -633,8 +651,9 @@ Meteor.methods({
* From Match patterns section
*/
var pat = { name: Match.Optional('test') };
check({ name: "something" }, pat) // OK
check({ name: "test" }, pat) // OK
check({}, pat) // OK
check({ name: "something" }, pat) // Throws an exception
check({ name: undefined }, pat) // Throws an exception
// Outside an object