diff --git a/codegen/text-manipulation.ts b/codegen/text-manipulation.ts index 66812bf..dea4e16 100644 --- a/codegen/text-manipulation.ts +++ b/codegen/text-manipulation.ts @@ -178,7 +178,7 @@ export function pall(array: Array, callbackfn: (value: T, index: number export function deconstruct(identifier: string | Array): Array { if (Array.isArray(identifier)) { - return [...values(identifier).linq.selectMany(deconstruct)]; + return [...values(identifier).selectMany(deconstruct)]; } return identifier. replace(/([a-z]+)([A-Z])/g, '$1 $2'). diff --git a/codemodel-v3/code-model/schema.ts b/codemodel-v3/code-model/schema.ts index 5d87080..ad2c7c9 100644 --- a/codemodel-v3/code-model/schema.ts +++ b/codemodel-v3/code-model/schema.ts @@ -119,11 +119,11 @@ export class Schema extends Extensions implements Schema { export function getPolymorphicBases(schema: Schema): Array { // are any of my parents polymorphic directly, or any of their parents? - return [...values(schema.allOf).linq.where(parent => parent.discriminator ? true : false), ...values(schema.allOf).linq.selectMany(getPolymorphicBases)]; + return [...values(schema.allOf).where(parent => parent.discriminator ? true : false), ...values(schema.allOf).selectMany(getPolymorphicBases)]; } export function getAllProperties(schema: Schema): Array { - return [...values(schema.allOf).linq.selectMany(getAllProperties), ...values(schema.properties)]; + return [...values(schema.allOf).selectMany(getAllProperties), ...values(schema.properties)]; } export function getAllPublicVirtualProperties(virtualProperties?: VirtualProperties): Array { @@ -133,7 +133,7 @@ export function getAllPublicVirtualProperties(virtualProperties?: VirtualPropert inlined: [] }; - return [...values([...props.owned, ...props.inherited, ...props.inlined]).linq.where(each => !each.private)]; + return [...values([...props.owned, ...props.inherited, ...props.inlined]).where(each => !each.private)]; } export function getAllVirtualProperties(virtualProperties?: VirtualProperties): Array { @@ -152,7 +152,7 @@ export function getVirtualPropertyFromPropertyName(virtualProperties: VirtualPro inherited: [], inlined: [] }; - return values([...props.owned, ...props.inherited, ...props.inlined]).linq.first(each => each.property.serializedName === propertyName); + return values([...props.owned, ...props.inherited, ...props.inlined]).first(each => each.property.serializedName === propertyName); } diff --git a/datastore/file-system.ts b/datastore/file-system.ts index 99522c9..7b7881b 100644 --- a/datastore/file-system.ts +++ b/datastore/file-system.ts @@ -19,7 +19,7 @@ export class MemoryFileSystem implements IFileSystem { public constructor(files: Map) { this.filesByUri = new Map( - items(files).linq.select( + items(files).select( each => [ ResolveUri(MemoryFileSystem.DefaultVirtualRootUri, each.key), each.value @@ -44,7 +44,7 @@ export class MemoryFileSystem implements IFileSystem { async EnumerateFileUris(folderUri: string = MemoryFileSystem.DefaultVirtualRootUri): Promise> { // return await [...From(this.filesByUri.keys()).Where(uri => { - return keys(this.filesByUri).linq.where(uri => { + return keys(this.filesByUri).where(uri => { // in folder? if (!uri.startsWith(folderUri)) { return false; @@ -53,7 +53,7 @@ export class MemoryFileSystem implements IFileSystem { // not in subfolder? // return uri.substr(folderUri.length).indexOf("/") === -1; return uri.substr(folderUri.length).indexOf('/') === -1; - }).linq.toArray(); + }).toArray(); //})]; } diff --git a/datastore/json-pointer.ts b/datastore/json-pointer.ts index e7693dd..e56c652 100644 --- a/datastore/json-pointer.ts +++ b/datastore/json-pointer.ts @@ -1,5 +1,5 @@ 'use strict'; -import { Dictionary, items, keys, values, any } from '@azure-tools/linq'; +import { Dictionary, items } from '@azure-tools/linq'; export type JsonPointer = string; export type JsonPointerTokens = Array; diff --git a/datastore/source-map/blaming.ts b/datastore/source-map/blaming.ts index 44f46f7..327b2ea 100644 --- a/datastore/source-map/blaming.ts +++ b/datastore/source-map/blaming.ts @@ -53,6 +53,6 @@ export class BlameTree { // recurse todos.push(...todo.blaming); } - return values(result).linq.distinct(x => JSON.stringify(x)).linq.toArray(); + return values(result).distinct(x => JSON.stringify(x)).toArray(); } } diff --git a/linq/common.ts b/linq/common.ts new file mode 100644 index 0000000..f122d0a --- /dev/null +++ b/linq/common.ts @@ -0,0 +1,18 @@ +export interface Index { + [key: number]: T; +} + +export interface Dictionary { + [key: string]: T; +} + +export class Dictionary implements Dictionary { +} + +export function ToDictionary(keys: Array, each: (index: string) => T) { + const result = new Dictionary(); + keys.map((v, i, a) => result[v] = each(v)); + return result; +} + +export type IndexOf = T extends Map ? T : T extends Array ? number : string; diff --git a/linq/exports.ts b/linq/exports.ts new file mode 100644 index 0000000..32df947 --- /dev/null +++ b/linq/exports.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export * from './new-linq'; +export * from './freeze'; +export * from './visitor'; +export * from './common'; diff --git a/linq/new-linq.ts b/linq/new-linq.ts new file mode 100644 index 0000000..79fa24f --- /dev/null +++ b/linq/new-linq.ts @@ -0,0 +1,301 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IndexOf, Dictionary } from './common'; + + +/* + * Reverses the elements in an Array. + * reverse(): T[]; + */ + + +export interface IterableWithLinq extends Iterable { + linq: IterableWithLinq; + any(predicate?: (each: T) => boolean): boolean; + all(predicate: (each: T) => boolean): boolean; + bifurcate(predicate: (each: T) => boolean): Array>; + concat(more: Iterable): IterableWithLinq; + distinct(selector?: (each: T) => any): IterableWithLinq; + first(predicate?: (each: T) => boolean): T | undefined; + selectNonNullable(selector: (each: T) => V): IterableWithLinq>; + select(selector: (each: T) => V): IterableWithLinq; + selectMany(selector: (each: T) => Iterable): IterableWithLinq; + where(predicate: (each: T) => boolean): IterableWithLinq; + forEach(action: (each: T) => void): void; + aggregate(accumulator: (current: T | A, next: T) => A, seed?: T | A, resultAction?: (result?: T | A) => A | R): T | A | R | undefined; + toArray(): Array; + + /** + * Gets or sets the length of the iterable. This is a number one higher than the highest element defined in an array. + */ + count(): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + +} + +/* eslint-disable */ + +function linqify(iterable: Iterable): IterableWithLinq { + if ((iterable)['linq'] === iterable) { + return >iterable; + } + const r = { + [Symbol.iterator]: iterable[Symbol.iterator].bind(iterable), + all: all.bind(iterable), + any: any.bind(iterable), + bifurcate: bifurcate.bind(iterable), + concat: concat.bind(iterable), + distinct: distinct.bind(iterable), + first: first.bind(iterable), + select: select.bind(iterable), + selectMany: selectMany.bind(iterable), + selectNonNullable: selectNonNullable.bind(iterable), + toArray: toArray.bind(iterable), + where: where.bind(iterable), + forEach: forEach.bind(iterable), + aggregate: aggregate.bind(iterable), + join: join.bind(iterable), + count: len.bind(iterable) + }; + r.linq = r; + return r; +} + +function len(this: Iterable): number { + return length(this); +} + +/** returns an IterableWithLinq<> for keys in the collection */ +export function keys | Dictionary | Map)>(source: TSrc & (Array | Dictionary | Map) | null | undefined): IterableWithLinq> { + if (source) { + if (Array.isArray(source)) { + return >>linqify((>source).keys()); + } + + if (source instanceof Map) { + return >>linqify((>source).keys()); + } + + return >>linqify((Object.getOwnPropertyNames(source))); + } + // undefined/null + return linqify([]); +} +function isIterable(source: any): source is Iterable { + return !!source && !!source[Symbol.iterator]; +} +/** returns an IterableWithLinq<> for values in the collection */ +export function values | Dictionary | Map)>(source: (Iterable | Array | Dictionary | Map) | null | undefined): IterableWithLinq { + if (source) { + // map + if (source instanceof Map) { + return linqify(source.values()); + } + + // any iterable source + if (isIterable(source)) { + return linqify(source); + } + + // dictionary (object keys) + return linqify(function* () { + for (const key of keys(source)) { + const value = source[key]; + if (typeof value !== 'function') { + yield value; + } + } + }()); + } + + + // null/undefined + return linqify([]); +} + +/** returns an IterableWithLinq<{key,value}> for the source */ +export function items | Dictionary | Map)>(source: TSrc & (Array | Dictionary | Map) | null | undefined): IterableWithLinq<{ key: IndexOf; value: T }> { + if (source) { + if (Array.isArray(source)) { + return ; value: T }>>linqify(function* () { for (let i = 0; i < source.length; i++) { yield { key: i, value: source[i] }; } }()); + } + + if (source instanceof Map) { + return ; value: T }>>linqify(function* () { for (const [key, value] of source.entries()) { yield { key, value }; } }()); + } + + return ; value: T }>>linqify(function* () { + for (const key of keys(source)) { + const value = source[key]; + if (typeof value !== 'function') { + yield { + key, value: source[key] + }; + } + } + }()); + } + // undefined/null + return linqify([]); +} + +export function length(source?: Iterable | Dictionary | Array | Map): number { + if (source) { + if (Array.isArray(source)) { + return source.length; + } + if (source instanceof Map) { + return source.size; + } + if (isIterable(source)) { + return [...source].length; + } + return source ? Object.getOwnPropertyNames(source).length : 0; + } + return 0; +} + +function any(this: Iterable, predicate?: (each: T) => boolean): boolean { + for (const each of this) { + if (!predicate || predicate(each)) { + return true; + } + } + return false; +} + +function all(this: Iterable, predicate: (each: T) => boolean): boolean { + for (const each of this) { + if (!predicate(each)) { + return false; + } + } + return true; +} + +function concat(this: Iterable, more: Iterable): IterableWithLinq { + return linqify(function* (this: Iterable) { + for (const each of this) { + yield each; + } + for (const each of more) { + yield each; + } + }.bind(this)()); +} + +function select(this: Iterable, selector: (each: T) => V): IterableWithLinq { + return linqify(function* (this: Iterable) { + for (const each of this) { + yield selector(each); + } + }.bind(this)()); +} + +function selectMany(this: Iterable, selector: (each: T) => Iterable): IterableWithLinq { + return linqify(function* (this: Iterable) { + for (const each of this) { + for (const item of selector(each)) { + yield item; + } + } + }.bind(this)()); +} + +function where(this: Iterable, predicate: (each: T) => boolean): IterableWithLinq { + return linqify(function* (this: Iterable) { + for (const each of this) { + if (predicate(each)) { + yield each; + } + } + }.bind(this)()); +} + +function forEach(this: Iterable, action: (each: T) => void) { + for (const each of this) { + action(each); + } +} + +function aggregate(this: Iterable, accumulator: (current: T | A, next: T) => A, seed?: T | A, resultAction?: (result?: T | A) => A | R): T | A | R | undefined { + let result: T | A | undefined = seed; + for (const each of this) { + if (result === undefined) { + result = each; + continue; + } + result = accumulator(result, each); + } + return resultAction !== undefined ? resultAction(result) : result; +} + +function selectNonNullable(this: Iterable, selector: (each: T) => V): IterableWithLinq> { + return linqify(function* (this: Iterable) { + for (const each of this) { + const value = selector(each); + if (value) { + yield >value; + } + } + }.bind(this)()); +} + +function nonNullable(this: Iterable): IterableWithLinq> { + return linqify(function* (this: Iterable) { + for (const each of this) { + if (each) { + yield >each; + } + } + }.bind(this)()); +} + +function first(this: Iterable, predicate?: (each: T) => boolean): T | undefined { + for (const each of this) { + if (!predicate || predicate(each)) { + return each; + } + } + return undefined; +} + +function toArray(this: Iterable): Array { + return [...this]; +} + +function join(this: Iterable, separator: string): string { + return [...this].join(separator); +} + +function bifurcate(this: Iterable, predicate: (each: T) => boolean): Array> { + const result = [new Array(), new Array()]; + for (const each of this) { + result[predicate(each) ? 0 : 1].push(each); + } + return result; +} + +function distinct(this: Iterable, selector?: (each: T) => any): IterableWithLinq { + const hash = new Dictionary(); + return linqify(function* (this: Iterable) { + + if (!selector) { + selector = i => i; + } + for (const each of this) { + const k = JSON.stringify(selector(each)); + if (!hash[k]) { + hash[k] = true; + yield each; + } + } + }.bind(this)()); +} \ No newline at end of file diff --git a/linq/main.ts b/linq/old-linq.ts similarity index 78% rename from linq/main.ts rename to linq/old-linq.ts index ab31592..921f03c 100644 --- a/linq/main.ts +++ b/linq/old-linq.ts @@ -2,28 +2,24 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export * from './freeze'; -export * from './visitor'; +import { IndexOf, Dictionary } from './common'; -export interface Index { - [key: number]: T; +export interface Linqed extends Iterable { + any(predicate?: (each: T) => boolean): boolean; + all(predicate: (each: T) => boolean): boolean; + bifurcate(predicate: (each: T) => boolean): Array>; + concat(more: Iterable): Linqable; + distinct(selector?: (each: T) => any): Linqable; + first(predicate?: (each: T) => boolean): T | undefined; + selectNonNullable(selector: (each: T) => V): Linqable>; + select(selector: (each: T) => V): Linqable; + selectMany(selector: (each: T) => Iterable): Linqable; + where(predicate: (each: T) => boolean): Linqable; + forEach(action: (each: T) => void): void; + aggregate(accumulator: (current: T | A, next: T) => A, seed?: T | A, resultAction?: (result?: T | A) => A | R): T | A | R | undefined; + toArray(): Array; } -export interface Dictionary { - [key: string]: T; -} - -export class Dictionary implements Dictionary { -} - -export function ToDictionary(keys: Array, each: (index: string) => T) { - const result = new Dictionary(); - keys.map((v, i, a) => result[v] = each(v)); - return result; -} - -export type IndexOf = T extends Map ? T : T extends Array ? number : string; - export interface Linqable extends Iterable { linq: { any(predicate?: (each: T) => boolean): boolean; @@ -41,14 +37,33 @@ export interface Linqable extends Iterable { toArray(): Array; }; } +/* eslint-disable */ + +function enlinq(iterable: Iterable): Linqed { + return { + ...iterable, + all: all.bind(iterable), + any: any.bind(iterable), + bifurcate: bifurcate.bind(iterable), + concat: concat.bind(iterable), + distinct: distinct.bind(iterable), + first: first.bind(iterable), + select: select.bind(iterable), + selectMany: selectMany.bind(iterable), + selectNonNullable: selectNonNullable.bind(iterable), + toArray: toArray.bind(iterable), + where: where.bind(iterable), + forEach: forEach.bind(iterable), + aggregate: aggregate.bind(iterable), + }; +} function linqify(iterable: Iterable): Linqable { - if (!!(iterable)['linq']) { + if ((iterable)['linq']) { return >iterable; } return Object.defineProperty(iterable, 'linq', { get: () => { - /* eslint-disable */ return { all: all.bind(iterable), any: any.bind(iterable), @@ -111,10 +126,41 @@ export function values | Dictionary | Map) }()); } + // null/undefined return linqify([]); } + +/** returns an Linqable<> for values in the collection */ +export function evalues | Dictionary | Map)>(source: (Iterable | Array | Dictionary | Map) | null | undefined): Linqed { + if (source) { + // map + if (source instanceof Map) { + return enlinq(source.values()); + } + + // any iterable source + if (isIterable(source)) { + return enlinq(source); + } + + // dictionary (object keys) + return enlinq(function* () { + for (const key of keys(source)) { + const value = source[key]; + if (typeof value !== 'function') { + yield value; + } + } + }()); + } + + + // null/undefined + return enlinq([]); +} + /** returns an Linqable<{key,value}> for the Collection */ export function items | Dictionary | Map)>(source: TSrc & (Array | Dictionary | Map) | null | undefined): Linqable<{ key: IndexOf; value: T }> { if (source) { diff --git a/linq/package.json b/linq/package.json index b0637f6..f5e5cb1 100644 --- a/linq/package.json +++ b/linq/package.json @@ -3,8 +3,8 @@ "version": "3.0.0", "patchOffset": 100, "description": "LINQ-like functionality for Typescript.", - "main": "./dist/main.js", - "typings": "./dist/main.d.ts", + "main": "./dist/exports.js", + "typings": "./dist/exports.d.ts", "engines": { "node": ">=10.12.0" }, diff --git a/linq/test/distinct.ts b/linq/test/distinct.ts index 3ca8034..9bf0af2 100644 --- a/linq/test/distinct.ts +++ b/linq/test/distinct.ts @@ -1,6 +1,6 @@ import { suite, test, slow, timeout, skip, only } from 'mocha-typescript'; import * as assert from 'assert'; -import { values, length } from '../main'; +import { values, length, evalues, } from '../old-linq'; @suite class MyTests { diff --git a/linq/test/new-linq.ts b/linq/test/new-linq.ts new file mode 100644 index 0000000..42614e6 --- /dev/null +++ b/linq/test/new-linq.ts @@ -0,0 +1,36 @@ +import { suite, test, slow, timeout, skip, only } from 'mocha-typescript'; +import * as assert from 'assert'; +import { values, length, } from '../new-linq'; + +@suite class NewLinq { + + private anArray = ['A', 'B', 'C', 'D', 'E']; + + @test async 'distinct'() { + + const items = ['one', 'two', 'two', 'three']; + const distinct = values(items).distinct().toArray(); + assert.equal(length(distinct), 3); + + const dic = { + happy: 'hello', + sad: 'hello', + more: 'name', + maybe: 'foo', + }; + + const result = values(dic).distinct().toArray(); + assert.equal(length(distinct), 3); + } + + @test async 'iterating thru collections'() { + // items are items. + assert.equal([...values(this.anArray)].join(','), this.anArray.join(',')); + + assert.equal(values(this.anArray).count(), 5); + + } + + +} +