├── .gitignore ├── common └── types.ts ├── tsc-fun ├── code-mod-with_ts-morph │ ├── modify-me.ts │ ├── readme.md │ └── instrumentor.ts └── without-abstructions │ └── type-exp.ts ├── .prettierrc ├── oop-behavioral ├── strategy │ ├── strategy-sequence-uml.puml │ └── demo.ts ├── mixins │ ├── README.md │ └── demo.ts └── visitor │ ├── README.md │ └── demo.ts ├── type-system ├── toolbelt.ts ├── lookup.ts ├── dynamic_dispatch.ts ├── utilities_ex.ts └── conditional-typing.ts ├── parsers ├── run-dummy-parser.ts ├── readme.md └── my-parser.ts ├── .eslintrc.json ├── functional ├── error-handling │ ├── readme.md │ └── error-classifier.ts ├── thunk.ts ├── compose.ts ├── semigroup.ts ├── promise │ ├── README.md │ └── demo.ts ├── foldl.ts ├── monoid.ts ├── functor │ ├── README.md │ ├── demo.ts │ └── experimental.ts └── algebric-data-types.ts ├── dependency-injection ├── src │ └── diy │ │ ├── injectable.ts │ │ └── injector.ts ├── example │ ├── services │ │ ├── color.service.ts │ │ ├── renderer.service.ts │ │ └── drawer.service.ts │ └── di-bootstrap.ts └── DI.md ├── README.md ├── package.json ├── tsconfig.json └── swc └── getFnNames.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | ignore 5 | private 6 | -------------------------------------------------------------------------------- /common/types.ts: -------------------------------------------------------------------------------- 1 | export interface Ctr { new(...args: any[]): T; } 2 | -------------------------------------------------------------------------------- /tsc-fun/code-mod-with_ts-morph/modify-me.ts: -------------------------------------------------------------------------------- 1 | class Runner { 2 | run() { 3 | console.log('run'); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tsc-fun/code-mod-with_ts-morph/readme.md: -------------------------------------------------------------------------------- 1 | [ts-morph](https://ts-morph.com/) supplies an easy to use abstraction on top of tsc 2 | 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "semi": true, 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /oop-behavioral/strategy/strategy-sequence-uml.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | Context -> Strategy1: algorithm(); 3 | Context <-- Strategy1: return result 4 | 5 | Context -> Strategy2: algorithm(); 6 | Context <-- Strategy2: return result 7 | @enduml 8 | -------------------------------------------------------------------------------- /type-system/toolbelt.ts: -------------------------------------------------------------------------------- 1 | // great post by the author of toolbelt 2 | // https://medium.com/free-code-camp/typescript-curry-ramda-types-f747e99744ab 3 | 4 | import { Tail } from 'ts-toolbelt/out/List/Tail'; 5 | type EvenNum = [2, 4, 6, 8]; 6 | 7 | const tail: Tail = [4, 6, 8]; 8 | const nontail: Tail = [4, 6]; //type err 9 | -------------------------------------------------------------------------------- /parsers/run-dummy-parser.ts: -------------------------------------------------------------------------------- 1 | import { parseDummyQL } from './my-parser'; 2 | 3 | function prettyPrint(x: any) { 4 | let opts = { depth: null, colors: 'auto' }; 5 | console.log(x, opts); 6 | } 7 | 8 | let textWithSpaces = `foo == "hey there" && foo == "eatPizza"`; 9 | 10 | let ast = parseDummyQL(textWithSpaces); 11 | prettyPrint(ast); 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "rules": { 12 | "quotes": ["error", "single"], 13 | "typescript-eslint/ban-ts-comment": "off" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /type-system/lookup.ts: -------------------------------------------------------------------------------- 1 | interface Task { 2 | id: number; 3 | title: string; 4 | completed: boolean; 5 | } 6 | 7 | const task: Task = { 8 | id: 666, 9 | title: 'Open a bug', 10 | completed: false 11 | }; 12 | 13 | function lookup(obj: T, key: K): T[K] { 14 | return obj[key]; 15 | } 16 | 17 | const id = lookup(task,'id'); 18 | console.log(id); 19 | -------------------------------------------------------------------------------- /functional/error-handling /readme.md: -------------------------------------------------------------------------------- 1 | - My [blog post](https://lironhazan.medium.com/designing-an-opinionated-functional-api-typescript-e7e89e8ab338) on that topic. 2 | 3 | - A nice [thread](https://stackoverflow.com/questions/48111558/functional-programming-how-to-handle-exceptions-in-functional-programming-or-wh) in stackoverflow 4 | - A great post from [fp-complete](https://www.fpcomplete.com/blog/error-handling-is-hard/) 5 | -------------------------------------------------------------------------------- /dependency-injection/src/diy/injectable.ts: -------------------------------------------------------------------------------- 1 | import {Ctr} from "../../../common/types"; 2 | type ClazzDecorator = (target: T) => void; 3 | 4 | export function Injectable (): ClazzDecorator> { 5 | return (target: Ctr) => { 6 | // this is needed so the design:paramtypes could be collected 7 | console.log('inside: Injectable decorator'); 8 | console.log(target.name, ' is used'); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /dependency-injection/example/services/color.service.ts: -------------------------------------------------------------------------------- 1 | export class ColorService { 2 | 3 | constructor() { 4 | console.log('init ColorService'); 5 | } 6 | 7 | private _color: string = 'red'; 8 | 9 | colorPicker = ['red', 'blue', 'green']; 10 | 11 | set color(color: string) { 12 | this._color = color; 13 | } 14 | 15 | get color(): string { 16 | return this._color; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /functional/thunk.ts: -------------------------------------------------------------------------------- 1 | // delay something, lazy eval 2 | 3 | function registerAction(calc: () => T): () => T { 4 | let result: T; 5 | return (): T => { 6 | // thunk ( a code to be evaluated at a later time, so it’s an appropriate name for a closure that gets stored ) 7 | if (result) return result; 8 | result = calc(); 9 | return result; 10 | }; 11 | } 12 | 13 | const imLazy = registerAction(() => 5); 14 | console.log('....life gos on'); 15 | console.log(imLazy()); 16 | -------------------------------------------------------------------------------- /dependency-injection/example/services/renderer.service.ts: -------------------------------------------------------------------------------- 1 | // renderer/updater service 2 | 3 | import {Injectable} from "../../src/diy/injectable"; 4 | import {DrawerService} from "./drawer.service"; 5 | 6 | @Injectable() 7 | export class RendererService { 8 | 9 | constructor(private drawer: DrawerService){ 10 | console.log('init RendererService'); 11 | } 12 | 13 | draw() { 14 | this.drawer.drawShape('line'); 15 | this.drawer.paint(); 16 | console.log('updating drawing'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /functional/compose.ts: -------------------------------------------------------------------------------- 1 | function compose(f: Function, g: Function) { 2 | return (x: T) => f(g(x)); 3 | } 4 | 5 | function normalize(str: string): string { 6 | return str.trim().toLowerCase(); // I don't mind mutating.. 7 | } 8 | 9 | function addSufix(str: string) { 10 | return str.concat('666'); 11 | } 12 | 13 | const result = compose(normalize, addSufix)('MY_NAME_IS_LIRON'); 14 | console.log(result); 15 | console.log(compose(normalize, addSufix)('MY_NAME_IS_LIRON') === compose(addSufix, normalize)('MY_NAME_IS_LIRON')); 16 | -------------------------------------------------------------------------------- /functional/semigroup.ts: -------------------------------------------------------------------------------- 1 | // The Semigroup represents a set with an associative binary operation. 2 | // This makes a semigroup a superset of monoids. 3 | // Semigoups have no other restrictions, and are a very general typeclass. 4 | // (a <> b) <> c == a <> (b <> c) 5 | // https://wiki.haskell.org/Data.Semigroup 6 | import { add } from './foldl'; 7 | 8 | interface Semigroup { 9 | concat: (a: T, b: T) => T 10 | } 11 | 12 | function getSemiSum(): Semigroup { 13 | return { concat: add } 14 | } 15 | 16 | const sumSemiGroup = getSemiSum(); 17 | sumSemiGroup.concat(1, 3); 18 | -------------------------------------------------------------------------------- /parsers/readme.md: -------------------------------------------------------------------------------- 1 | Parsers are fun, parsers combinators are fun to think of :) 2 | 3 | Let's use parsimmon to enforce the following dummy query lang: 4 | 5 | ```code 6 | let textWithSpaces = `foo == "hey there" && foo == "eatPizza"`; 7 | let text = `foo=="hey there"`; 8 | ``` 9 | 10 | foo == "hey there" 11 | foo != "cool" 12 | 13 | BTW - Chevrotain is a great choice (I really like it..) for building robust parsers as well, more of a DSLish 14 | style, comes with a lexer, has best performance according to 15 | [this banchmark](https://chevrotain.io/performance/) and overall fun to work with. 16 | 17 | -------------------------------------------------------------------------------- /type-system/dynamic_dispatch.ts: -------------------------------------------------------------------------------- 1 | interface Instrument { 2 | play: () => void; 3 | } 4 | 5 | class Guitar implements Instrument { 6 | play() { 7 | console.log(`drawing Circle`) 8 | } 9 | } 10 | 11 | class Drums implements Instrument { 12 | play() { 13 | console.log(`drawing Rectangle`) 14 | } 15 | } 16 | 17 | // dynamic dispatch 18 | const playPart:PlayFn = (instrumets) => { 19 | instrumets.forEach(instrument => instrument.play()); 20 | }; 21 | 22 | playPart([new Guitar(), new Drums()]); 23 | 24 | 25 | 26 | type PlayFn = (instruments: I[]) => void; 27 | -------------------------------------------------------------------------------- /functional/promise/README.md: -------------------------------------------------------------------------------- 1 | The Promise: 2 | 3 | A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved. 4 | 5 | Promised is often mentioned as a "monad". 6 | 7 | According to the book by Luis Atencio: "Functional Programming in JavaScript - How to Improve Your JavaScript Programs": 8 | 9 | ` "Let’s explore the Promise monad. Just to give you a rough idea, imagine a monad that wraps a long 10 | computation (this isn’t the actual Promise interface, but a close analogy): 11 | Promise.of().map(fun1).map(fun2);//-> Promise(result)"` 12 | -------------------------------------------------------------------------------- /dependency-injection/DI.md: -------------------------------------------------------------------------------- 1 | #Dependency Injection: 2 | 3 | - Do it yourself. 4 | - Using InversifyJS. 5 | 6 | Description: When one object supplies the dependencies of another object. 7 | 8 | requirements: 9 | - Had to install: 'reflect-metadata'. 10 | - Have to decorate our injectables so they'll emit meta data. 11 | - In tsconfig enable "Experimental Options": 12 | 13 | `"experimentalDecorators": true, 14 | "emitDecoratorMetadata": true` 15 | 16 | Related [Blog post](https://medium.com/@Sentinelone_tech/dependency-injection-in-typescript-from-scratch-d1a4422043a0) was published to the sentinelOne tech publication 17 | -------------------------------------------------------------------------------- /dependency-injection/example/services/drawer.service.ts: -------------------------------------------------------------------------------- 1 | // canvas/other drawer service 2 | 3 | import { Injectable } from "../../src/diy/injectable"; 4 | import {ColorService} from "./color.service"; 5 | 6 | @Injectable() 7 | export class DrawerService { 8 | 9 | constructor(private colorService: ColorService){ 10 | console.log('init DrawerService'); 11 | } 12 | 13 | drawShape(shape: 'line' | 'circle') { 14 | console.log('drawing shape: ', shape); 15 | } 16 | 17 | paint() { 18 | this.colorService.color = this.colorService.colorPicker[1]; 19 | console.log('paint to ', this.colorService.color); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dependency-injection/example/di-bootstrap.ts: -------------------------------------------------------------------------------- 1 | import {Injector, Resolver} from "../src/diy/injector"; 2 | import {RendererService} from "./services/renderer.service"; 3 | 4 | class DiBootstrap { 5 | 6 | static run() { 7 | console.log('---------------- | START | -----------------'); 8 | 9 | // Static injector 10 | // const renderer = Injector.resolve(RendererService); 11 | // renderer.draw(); 12 | 13 | const renderer: RendererService = Resolver.resolve(RendererService); 14 | renderer.draw(); 15 | 16 | console.log('---------------- | END | -----------------'); 17 | } 18 | } 19 | 20 | DiBootstrap.run(); 21 | -------------------------------------------------------------------------------- /oop-behavioral/mixins/README.md: -------------------------------------------------------------------------------- 1 | 2 | Definition: 3 | 4 | Mixin programming is a style of software development, 5 | in which units of functionality are created in a class and then mixed in with other classes. 6 | 7 | ** Disclaimer - Generally I prefer to avoid inheriting classes due to the "coupling" it causes, but in specific cases 8 | in which you'll want to "decorate" a certain class with another classes specific behaviors, this is kind of a nice approach. 9 | This implementation is quit simple and only involve 2 classes at a time, you can basically implement your own merge function that could iterate on more than 2 classes. 10 | 11 | Read my [related post](https://itnext.io/exploring-the-mixin-pattern-by-code-1dbe5e3124eb). 12 | -------------------------------------------------------------------------------- /functional/foldl.ts: -------------------------------------------------------------------------------- 1 | export type BinOp = (a: number, b: number) => number; 2 | export const add: BinOp = (num1: number, num2: number) => num1 + num2; 3 | 4 | const foldl = (binOp: BinOp, list: number[], initVal = 0) => { 5 | return list.reduce((acc, val) => { return binOp(acc, val)}, initVal); 6 | }; 7 | 8 | const foldr = (binOp: BinOp, list: number[], initVal = 0) => { 9 | return list.reduceRight((acc, val) => { return binOp(acc, val)}, initVal); 10 | }; 11 | 12 | const res1 = foldl(add, [1, 2, 3, 4]); 13 | const res2 = foldr(add, [1, 2, 3, 4]); 14 | // 10 15 | 16 | console.log(res1 === res2); // associativity kept 17 | 18 | type first = ([a, b]: [T, X]) => T 19 | const fst: first = ([a]) => a; 20 | console.log(fst(['foo', 666])); 21 | 22 | 23 | -------------------------------------------------------------------------------- /tsc-fun/code-mod-with_ts-morph/instrumentor.ts: -------------------------------------------------------------------------------- 1 | import { Project } from 'ts-morph'; 2 | import { config } from '../../private/config'; 3 | 4 | export async function injectDummyProfiler(filePath: string, className: string, methodName: string) { 5 | const project = new Project(); 6 | project.addSourceFileAtPathIfExists(filePath); 7 | const sourceFile = project.getSourceFiles()[0]; 8 | const method = sourceFile.getClassOrThrow(className).getMethodOrThrow(methodName); 9 | 10 | method.insertStatements(0, `console.time('profiling');`); 11 | method.addStatements(`console.timeEnd('profiling');`); 12 | 13 | console.log(sourceFile.getFullText()); 14 | await project.save(); 15 | } 16 | 17 | injectDummyProfiler(config.filepath, 'Runner', 'run').then((_) => console.log('done')); 18 | -------------------------------------------------------------------------------- /functional/monoid.ts: -------------------------------------------------------------------------------- 1 | // the Monoid typeclass is a class for types which have a single most natural operation for combining values, 2 | // together with a value which doesn't do anything when you combine it with others (this is called the identity element). 3 | // https://wiki.haskell.org/Monoid 4 | 5 | import { add } from './foldl'; 6 | 7 | interface Monoid { 8 | empty: () => T; 9 | combine: (a: T, b: T) => T 10 | } 11 | 12 | function getSumMonoid(): Monoid { 13 | return { 14 | empty: () => 0, 15 | combine: add 16 | } 17 | } 18 | 19 | const sumMonoid = getSumMonoid(); 20 | console.log(sumMonoid.empty()); 21 | // 0 22 | 23 | console.log(sumMonoid.combine(6, 2)); 24 | console.log(sumMonoid.combine(2, 6) === sumMonoid.combine(6, 2)); // associative 25 | // true 26 | 27 | console.log() 28 | -------------------------------------------------------------------------------- /tsc-fun/without-abstructions/type-exp.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript'; 2 | 3 | const source = ` 4 | const sixsixsix = 666; 5 | `; 6 | 7 | function numToStringTransformer(): ts.TransformerFactory { 8 | return (context) => { 9 | const visit: ts.Visitor = (node) => { 10 | if (ts.isNumericLiteral(node)) { 11 | // check why is it deprecaated 12 | return ts.createStringLiteral(node.text); 13 | } 14 | return ts.visitEachChild(node, (child) => visit(child), context); 15 | }; 16 | 17 | return (node) => ts.visitNode(node, visit); 18 | }; 19 | } 20 | 21 | let result = ts.transpileModule(source, { 22 | compilerOptions: { module: ts.ModuleKind.ESNext }, 23 | transformers: { before: [numToStringTransformer()] }, 24 | }); 25 | 26 | console.log(result.outputText); 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Advanced patterns in Typescript 2 | 3 | Following repo was started as a collection of code references/concepts for my personal blogs. 4 | 5 | [Liron Hazan @medium](https://medium.com/@lironhazan) 6 | 7 | [Type thinking @devto](https://dev.to/lironn_h/type-thinking-p1-types-3j94) 8 | 9 | 10 | You're more than welcome to fork and open PR's with your educative awesome examples :) 11 | 12 | Content: 13 | - DI with decorators (Metaprogramming). 14 | - Type system concepts 15 | - Useful Gof DP (mainly behavioral). 16 | - Functional concepts. 17 | - Compiler tricks. 18 | - Parsers 19 | - Any paradigm for writing better software 20 | 21 | 22 | 23 | 24 | In order to run each demo (of the design patterns): 25 | `ts-node ./folder/*/demo.ts` 26 | 27 | OR 28 | 29 | `tsc filename.ts && node filename.js` 30 | 31 | Other examples could be executed using ts-node 32 | -------------------------------------------------------------------------------- /functional/functor/README.md: -------------------------------------------------------------------------------- 1 | What is a Functor? 2 | 3 | According to the book by Luis Atencio: "Functional Programming in JavaScript - How to Improve Your JavaScript Programs": 4 | 5 | `"In essence, a functor is nothing more than a data structure that you can map functions over with the purpose of lifting values into a wrapper, modifying them, and then 6 | putting them back into a wrapper. It’s a design pattern that defines semantics for how 7 | fmap should work. Here’s the general definition of fmap: 8 | fmap :: (A -> B) -> Wrapper(A) -> Wrapper(B)"` 9 | 10 | The most commonly used functor example is the Array - it contains the Array.map() 11 | (and other higher order functions used for transformation) 12 | using the map function we modify the giving data and return a new array ref (the wrapper). 13 | 14 | BTW I also recommend reading Eric Elliot's [post](https://medium.com/javascript-scene/javascript-monads-made-simple-7856be57bfe8) on Functors and Monads 15 | -------------------------------------------------------------------------------- /parsers/my-parser.ts: -------------------------------------------------------------------------------- 1 | import Parsimmon from 'parsimmon'; 2 | const P = Parsimmon; 3 | 4 | let MyFooQueryLang = P.createLanguage({ 5 | // `r` eq rules. 6 | dummy_query: (r) => r.expression.many(), 7 | 8 | expression: (r) => P.alt(r.base, r.sub), 9 | 10 | base: (r) => P.seq(r.field, r.operator, r.value), 11 | sub: (r) => P.seq(P.alt(r.and, r.or), r.base), 12 | 13 | field: () => P.string('foo').skip(P.optWhitespace).desc('field'), 14 | 15 | operator: () => P.string('==').skip(P.optWhitespace).desc('operator'), 16 | 17 | and: () => P.string('&&').skip(P.optWhitespace).desc('and'), 18 | or: () => P.string('||').skip(P.optWhitespace).desc('or'), 19 | 20 | value: () => 21 | P.string('"') 22 | .then(P.regex(/[^"]+/)) 23 | .map((lifted) => `${lifted} 🍕`) // fp awesomeness 🤟 24 | .skip(P.string('"')) 25 | .skip(P.optWhitespace) 26 | .desc('value'), 27 | }); 28 | 29 | export function parseDummyQL(query: string): T { 30 | return MyFooQueryLang.dummy_query.tryParse(query); 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advanced-patterns-in-typescript", 3 | "version": "1.0.0", 4 | "description": "ts is powerful", 5 | "main": "index.ts", 6 | "scripts": { 7 | "start:di": "ts-node -O '{\"module\": \"commonjs\"}' ./dependency-injection/example/di-bootstrap.ts", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [ 12 | "typescript", 13 | "design", 14 | "patterns" 15 | ], 16 | "author": "Liron Hazan", 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@types/node": "^15.3.0", 20 | "eslint": "^7.22.0", 21 | "eslint-config-prettier": "^8.3.0", 22 | "jest": "^26.6.3", 23 | "prettier": "^2.2.1", 24 | "ts-node": "^9.1.1", 25 | "typescript": "^4.2.4" 26 | }, 27 | "dependencies": { 28 | "@types/parsimmon": "^1.10.6", 29 | "parsimmon": "^1.17.0", 30 | "reflect-metadata": "^0.1.13", 31 | "ts-morph": "^10.0.2", 32 | "ts-toolbelt": "^9.6.0", 33 | "@swc/core": "^1.2.197" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /type-system/utilities_ex.ts: -------------------------------------------------------------------------------- 1 | // ADTs 2 | 3 | // Product 4 | interface PageDetail { 5 | index: number; 6 | flag?: boolean; 7 | } 8 | //Sum 9 | type Page = 'home' | 'about' | 'contact'; // sum type (home+about+contact) 10 | // Product 11 | type Site = Record; // product type (home*about*concat) 12 | 13 | const mySite: Site = { 14 | about: { index: 1 }, 15 | contact: { index: 2 }, 16 | home: { index: 0 }, 17 | }; 18 | 19 | // Parameters 20 | type DummyProfilerArgs = Parameters; 21 | type DummyProfilerFN = (...args: DummyProfilerArgs) => void; 22 | function injectDummyProfiler(filePath: string, className: string, methodName: string): void {} 23 | 24 | // Required: 25 | 26 | // the interesting part of the Required implementation is --> -? 27 | // which cancels the optional ? 28 | // type Required = { 29 | // [P in keyof T]-?: T[P]; 30 | // }; 31 | type RPage = Required; 32 | const page: PageDetail = { 33 | index: 0, 34 | }; 35 | 36 | const rpage: RPage = { 37 | // Property 'flag' is missing in type '{ index: number; }' but required in type 'Required'. 38 | index: 0, 39 | }; 40 | -------------------------------------------------------------------------------- /dependency-injection/src/diy/injector.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import {Ctr} from "../../../common/types"; 3 | 4 | export class Injector { 5 | 6 | private depInstances: Map> = new Map>(); 7 | 8 | // Not storing an instances map 9 | static resolve(target: Ctr): T { 10 | const tokens = Reflect.getMetadata('design:paramtypes', target) || []; 11 | const injections = tokens.map((token: any) => Injector.resolve(token)); 12 | return new target(...injections); 13 | } 14 | 15 | // Storing Instances map so a service will only have one instance 16 | resolve(target: Ctr): any { 17 | if (this.depInstances && this.depInstances.has(target.name)) { 18 | console.log(target.name, 'instance exists'); 19 | return this.depInstances.get(target.name); 20 | } 21 | 22 | const tokens = Reflect.getMetadata('design:paramtypes', target) || []; 23 | const injections = tokens.map((token: any) => Resolver.resolve(token)); 24 | this.depInstances.set(target.name, target); 25 | 26 | console.log(this.depInstances); 27 | 28 | return new target(...injections); 29 | } 30 | } 31 | 32 | export const Resolver = new Injector(); 33 | -------------------------------------------------------------------------------- /type-system/conditional-typing.ts: -------------------------------------------------------------------------------- 1 | // simple conditional typing 2 | 3 | import { List } from 'ts-toolbelt/out/List/List'; 4 | 5 | type Head = T extends [any, ...any[]] ? T[0] : never; 6 | type testH = Head<[1, 2, 3, 4]>; 7 | const one: testH = 1; // valid 8 | const two: testH = 2; // Type '2' is not assignable to type '1' 9 | 10 | interface BarModel { 11 | tag: string; 12 | index: number; 13 | parent: BarModel | null; 14 | } 15 | 16 | type MaybeChild = A extends { 17 | tag: string; 18 | index: number; 19 | } 20 | ? BarModel 21 | : A; 22 | 23 | const child: MaybeChild = { 24 | tag: 'foo', 25 | index: 0, 26 | parent: { tag: 'fooParent', index: 1, parent: null }, 27 | }; 28 | 29 | const nonChild: MaybeChild<{ tag: string }> = { 30 | tag: 'baz', 31 | }; 32 | 33 | // researched the use of "infer" keyword - my findings: 34 | 35 | // vscode - used it about ~ 7 times 36 | 37 | // type ComputedEditorOptionValue> = 38 | // T extends IEditorOption ? R : never; 39 | 40 | // copied from official utility types 41 | type ReturnTypeT any> = T extends (...args: any) => infer R ? R : any; 42 | 43 | type Parameters any> = T extends (...args: infer P) => any ? P : never; 44 | -------------------------------------------------------------------------------- /functional/functor/demo.ts: -------------------------------------------------------------------------------- 1 | class MyFunctorContext { 2 | 3 | items: T[]; 4 | 5 | constructor (items: T[]) { 6 | this.items = items 7 | } 8 | 9 | map(transformFn: (arg: T) => V ): MyFunctorContext { 10 | const newItems = []; 11 | 12 | for (const item of this.items) { 13 | newItems.push(transformFn(item)); 14 | } 15 | 16 | return new MyFunctorContext(newItems); 17 | } 18 | } 19 | // In Haskell btw: instance Functor Context where 20 | // fmap f (Context a) = Context (f a) 21 | 22 | 23 | 24 | const bananas: any = [ 25 | {isYellow: true}, 26 | {isYellow: false}, 27 | {isYellow: true}, 28 | {isYellow: false} 29 | ]; 30 | 31 | 32 | const myTypedFunctor = new MyFunctorContext(bananas); 33 | 34 | const bestBananas = myTypedFunctor 35 | .map((banana: any) => { 36 | if (banana.isYellow) { 37 | banana.tasty = true; 38 | return banana; 39 | } 40 | banana.tasty = false; 41 | return banana; 42 | }); 43 | 44 | console.log(bestBananas); 45 | 46 | // result 47 | // MyTypedFunctor 48 | // { 49 | // items: [ 50 | // {isYellow: true, hasMagnesium: true, tasty: true}, 51 | // {isYellow: false, hasMagnesium: true, tasty: false}, 52 | // {isYellow: true, hasMagnesium: true, tasty: true}, 53 | // {isYellow: false, hasMagnesium: true, tasty: false} 54 | // ] 55 | // } 56 | -------------------------------------------------------------------------------- /oop-behavioral/visitor/README.md: -------------------------------------------------------------------------------- 1 | Visitor Pattern is used to perform an action on a structure of elements without changing the implementation of the elements themselves. 2 | 3 | Players: 4 | 5 | Visitor - This is an interface or an abstract class used to declare the visit operations for all the types of visitable classes. Usually the name of the operation is the same and the operations are differentiated by the method signature: The input object type decides which of the method is called. 6 | ConcreteVisitor - For each type of visitor all the visit methods, declared in abstract visitor, must be implemented. Each Visitor will be responsible for different operations. When a new visitor is defined it has to be passed to the object structure. 7 | 8 | Visitable - is an abstraction which declares the accept operation. This is the entry point which enables an object to be "visited" by the visitor object. Each object from a collection should implement this abstraction in order to be able to be visited. 9 | ConcreteVisitable - Those classes implements the Visitable interface or class and defines the accept operation. The visitor object is passed to this object using the accept operation. 10 | 11 | ObjectStructure - This is a class containing all the objects that can be visited. It offers a mechanism to iterate through all the elements. This structure is not necessarily a collection. In can be a complex structure, such as a composite object. 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | // "lib": [], 6 | "strictNullChecks": true, 7 | "downlevelIteration": true, 8 | "sourceMap": true, /* Generates corresponding '.map' file. */ 9 | "outDir": "dist/", /* Redirect output structure to the directory. */ 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 12 | "types": [ 13 | "reflect-metadata" 14 | ], /* Type declaration files to be included in compilation. */ 15 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 16 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 17 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */ 18 | }, 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /oop-behavioral/strategy/demo.ts: -------------------------------------------------------------------------------- 1 | interface GraphModel { 2 | name: string; 3 | id: string; 4 | children: GraphModel[]; 5 | linksId: string[] 6 | } 7 | 8 | interface GraphUpdateStrategy { 9 | update(): void; 10 | onNodeClick(): void; 11 | onLinkHover(): void; 12 | } 13 | 14 | class FooGraphUpdater implements GraphUpdateStrategy{ 15 | 16 | onLinkHover(): void { 17 | console.log('hover on node', FooGraphUpdater.name); 18 | } 19 | 20 | onNodeClick(): void { 21 | console.log('clicked on node', FooGraphUpdater.name); 22 | } 23 | 24 | update(): void { 25 | console.log('updating foo', FooGraphUpdater.name); 26 | } 27 | 28 | } 29 | 30 | class BarGraphUpdater implements GraphUpdateStrategy{ 31 | onLinkHover(): void { 32 | console.log('hover on node', BarGraphUpdater.name); 33 | } 34 | 35 | onNodeClick(): void { 36 | console.log('clicked on node', BarGraphUpdater.name); 37 | } 38 | 39 | update(): void { 40 | console.log('updating foo', BarGraphUpdater.name); 41 | } 42 | 43 | } 44 | 45 | class GraphContext { 46 | graphUpdater: GraphUpdateStrategy; 47 | 48 | constructor(graphUpdater: GraphUpdateStrategy) { 49 | this.graphUpdater = graphUpdater; 50 | } 51 | 52 | clickNode() { 53 | this.graphUpdater.onNodeClick(); 54 | } 55 | } 56 | 57 | const graphComponentA = new GraphContext(new BarGraphUpdater()); 58 | graphComponentA.clickNode(); 59 | 60 | const graphComponentB = new GraphContext(new FooGraphUpdater()); 61 | graphComponentB.clickNode(); 62 | -------------------------------------------------------------------------------- /functional/functor/experimental.ts: -------------------------------------------------------------------------------- 1 | interface Constraintable { 2 | constraint: number; 3 | } 4 | 5 | 6 | class MyFunctorContext> { 7 | 8 | items: T[]; 9 | 10 | constructor (items: T[]) { 11 | this.items = items 12 | } 13 | 14 | map(transformFn: , V> (arg: T) => V ): MyFunctorContext { 15 | const newItems: T[] = []; 16 | 17 | for (const item of this.items) { 18 | newItems.push(transformFn(item)); 19 | } 20 | 21 | return new MyFunctorContext(newItems); 22 | } 23 | } 24 | // In Haskell btw: instance Functor Context where 25 | // fmap f (Context a) = Context (f a) 26 | 27 | 28 | 29 | const bananas: any = [ 30 | {isYellow: true}, 31 | {isYellow: false}, 32 | {isYellow: true}, 33 | {isYellow: false} 34 | ]; 35 | 36 | 37 | const myTypedFunctor = new MyFunctorContext(bananas); 38 | 39 | const bestBananas = myTypedFunctor 40 | .map((banana: any) => { 41 | if (banana.isYellow) { 42 | banana.tasty = true; 43 | return banana; 44 | } 45 | banana.tasty = false; 46 | return banana; 47 | }); 48 | 49 | console.log(bestBananas); 50 | 51 | // result 52 | // MyTypedFunctor 53 | // { 54 | // items: [ 55 | // {isYellow: true, hasMagnesium: true, tasty: true}, 56 | // {isYellow: false, hasMagnesium: true, tasty: false}, 57 | // {isYellow: true, hasMagnesium: true, tasty: true}, 58 | // {isYellow: false, hasMagnesium: true, tasty: false} 59 | // ] 60 | // } 61 | -------------------------------------------------------------------------------- /oop-behavioral/visitor/demo.ts: -------------------------------------------------------------------------------- 1 | interface TreeNode { 2 | marked: boolean; 3 | children: TreeNode[]; 4 | } 5 | 6 | interface NodeVisitor { 7 | visit(node: T): void; 8 | } 9 | 10 | class Visitor implements NodeVisitor { 11 | visit(node: TreeNode): void { 12 | node.marked = true; 13 | } 14 | 15 | // Opinion - I implemented a similar purpose visitor just in a static way without using 16 | // much abstractions 17 | static checkNode(node: TreeNode) { 18 | node.marked = true; 19 | } 20 | } 21 | 22 | interface Visitable { 23 | accept(visitor: NodeVisitor, node: TreeNode): void; 24 | } 25 | 26 | class TreeComponent implements Visitable { 27 | root: TreeNode = { marked: false, children: [{ marked: false, children: []}, { marked: false, children: [{ marked: false, children: []}]}]}; 28 | constructor(private visitor: Visitor) {} 29 | 30 | accept(visitor: NodeVisitor, node: TreeNode): void { 31 | visitor.visit(node); 32 | } 33 | 34 | checkNodes(root: TreeNode) { // dfs 35 | for (const node of root.children) { 36 | //Opinion: clearer--> Visitor.checkNode(node); 37 | this.accept(this.visitor, node); 38 | if (node.children.length !== 0) { 39 | this.checkNodes(node); 40 | } 41 | } 42 | } 43 | 44 | doSomething() { 45 | this.accept(this.visitor, this.root); 46 | if (this.root.children.length === 0) return; 47 | this.checkNodes(this.root); 48 | } 49 | } 50 | 51 | const tVisitor = new Visitor(); 52 | const tc = new TreeComponent(tVisitor); 53 | console.log(JSON.stringify(tc.root)); 54 | tc.doSomething(); 55 | console.log(JSON.stringify(tc.root)); 56 | 57 | -------------------------------------------------------------------------------- /oop-behavioral/mixins/demo.ts: -------------------------------------------------------------------------------- 1 | import {Ctr} from "../../common/types"; 2 | 3 | interface IElementX { 4 | name: string; 5 | classList: string[]; 6 | } 7 | 8 | const ElementX = { 9 | name: 'foo', 10 | classList: [] 11 | }; 12 | 13 | // Explicit mixin util 14 | const mixinStyle = (base: Ctr) : Ctr => { 15 | return class extends base { 16 | 17 | constructor(...args: any[]) { 18 | super(...args); 19 | } 20 | 21 | public addClass(element: IElementX, cssClass: string) { 22 | element.classList.push(cssClass); 23 | console.log(cssClass, 'was added'); 24 | } 25 | } 26 | }; 27 | 28 | 29 | class classB { 30 | constructor() {} 31 | 32 | private thing = 'Write code'; 33 | 34 | public doSomething() { 35 | return this.thing; 36 | } 37 | } 38 | 39 | const MixedClass: Ctr & typeof classB = mixinStyle(classB); 40 | 41 | // classA doesn't need to extend classB in order to get its methods - gets them by the MixedClass 42 | class classA extends MixedClass { 43 | constructor() { 44 | super(); 45 | } 46 | 47 | public useMixinFn() { 48 | return this.doSomething(); 49 | } 50 | } 51 | 52 | const instanceA = new classA; 53 | 54 | console.log(instanceA.useMixinFn()); 55 | instanceA.addClass(ElementX, 'sparkling'); 56 | new MixedClass().addClass(ElementX, 'invalid'); 57 | console.log(Reflect.has(instanceA, 'doSomething')); 58 | 59 | 60 | console.log('---------------------- playing with objects -------------------'); 61 | // Using Object assign for classes wont work, classes are constructor functions 62 | 63 | const mix = (obj1: Ctr, obj2: Ctr) => { 64 | return { ...obj1, ...obj2}; 65 | }; 66 | 67 | const mixed = mix(classA, classB); 68 | 69 | console.log(mixed); 70 | console.log(Reflect.has(mixed, 'doSomething')); 71 | -------------------------------------------------------------------------------- /functional/algebric-data-types.ts: -------------------------------------------------------------------------------- 1 | interface Point { 2 | x: number; 3 | y: number 4 | } 5 | const point: Point = { 6 | x: 10, 7 | y: 3 8 | } 9 | 10 | // ADT and examples.. 11 | // Sum types 12 | 13 | type Maybe = None | Just; // Option 14 | 15 | class Just { 16 | constructor(private value: A) {} 17 | 18 | map(f: (param: A) => B): Just { 19 | return new Just(f(this.value)); 20 | } 21 | } 22 | 23 | class None { 24 | map(): None { 25 | return this; 26 | } 27 | } 28 | 29 | // Maybe[A].map(fn: A => B) : Maybe[B] 30 | class MaybeFunctor { 31 | constructor(private value: A) {} 32 | 33 | map(f: (param: A) => B): Maybe { 34 | if (!this.value) { return new None() } 35 | return new Just(f(this.value)); 36 | } 37 | } 38 | 39 | // Transforms any 2D point to maybe 3D struct 40 | function fromMaybePoint(maybeP: Point | null): Maybe { 41 | return new MaybeFunctor(maybeP).map((p: any) => { 42 | p.x += 1; 43 | p.y += 1; 44 | p.z = 10; 45 | return p; 46 | }); 47 | } 48 | 49 | const noResult = fromMaybePoint(null); 50 | const pppoint = fromMaybePoint(point) 51 | .map(() => point) 52 | .map(() => point) 53 | .map(() => point); 54 | 55 | console.log(noResult); 56 | console.log(pppoint); 57 | 58 | 59 | // Either pattern for error handling 60 | type Either = Left | Right // Result 61 | 62 | class Right { // Option 63 | constructor(private value: A) {} 64 | 65 | map(f: (param: A) => B): Right { 66 | return new Right(f(this.value)); 67 | } 68 | } 69 | 70 | class Left { // Err 71 | constructor(private value: A) {} 72 | map(): Left { 73 | return new Left(this.value); 74 | } 75 | } 76 | 77 | function either(result: Point, injectErr?: () => {}): Either { 78 | try { 79 | injectErr && injectErr(); 80 | return new Right(result); 81 | } catch (e) { 82 | return new Left(new Error); 83 | } 84 | } 85 | 86 | const right = either(point); 87 | 88 | console.log(either(point, () => { throw new Error()})); 89 | console.log(right); 90 | 91 | 92 | // Product types 93 | type Pair = [A, B] 94 | 95 | -------------------------------------------------------------------------------- /functional/error-handling /error-classifier.ts: -------------------------------------------------------------------------------- 1 | // Real life example of a functional I implemented on one of my projects: 2 | 3 | export type Result = Error | Just; 4 | type Just = T; 5 | type Error = Just; //LOL :) 6 | 7 | export class ResultT { 8 | private result: Result; 9 | 10 | constructor(result: Result) { 11 | this.result = result; 12 | } 13 | 14 | /** 15 | * Result mapper 16 | * @param onSuccessFn 17 | */ 18 | ok(onSuccessFn: (lifted: T) => ResultT): ResultT { 19 | return isError(this.result) ? this : onSuccessFn(this.result as T); 20 | } 21 | 22 | /** 23 | * Error mapper 24 | * @param onFailureFn 25 | */ 26 | err(onFailureFn: (error: ErrorClassification) => ResultT): ResultT { 27 | return isError(this.result) ? onFailureFn(this.result as ErrorClassification) : this; 28 | } 29 | 30 | map(transformFn: (a: T) => A): ResultT { 31 | this.result = transformFn(this.result as T) as any; 32 | return this; 33 | } 34 | } 35 | 36 | export enum ErrorType { 37 | ErrOne = 'Unsupported', 38 | ErrTwo = 'Invalid', 39 | } 40 | 41 | export interface ErrorClassification { 42 | message: string; 43 | type: ErrorType; 44 | } 45 | 46 | /** 47 | * Since we don't have a type based pattern matching we're using "tags" runtime check 48 | * @param result 49 | */ 50 | export function isError(result: Result): result is E { 51 | return typeof result === 'object' && 'message' in result && 'type' in result; 52 | } 53 | 54 | // Example 55 | 56 | const err: ErrorClassification = { 57 | message: 'haha', 58 | type: ErrorType.ErrOne, 59 | }; 60 | 61 | const mockedErr = new ResultT(err); 62 | mockedErr 63 | .ok(() => mockedErr) // no operation in our case 64 | .err((err: ErrorClassification) => { 65 | console.log(err.message); 66 | err.message = 'transformed err message'; 67 | return mockedErr; 68 | }) 69 | .map(console.log); 70 | 71 | const result = new ResultT({ a: 'foo', b: 'bar' }); 72 | 73 | result 74 | .ok((res) => { 75 | res.a = 'not foo'; 76 | return result; 77 | }) 78 | .map((res: { a: any }) => ({ 79 | a: res.a, 80 | c: 'mako', 81 | })) 82 | .map(console.log); 83 | 84 | // Closer look into map: 85 | // Identity: object.map(x => x) ≍ object 86 | const isInstanceofResultT = result.map((a) => a) instanceof ResultT; 87 | console.log('isInstanceofResultT', isInstanceofResultT); 88 | 89 | function one(s: string) { 90 | const one = 'one'; 91 | return one + s; 92 | } 93 | function two(s: string) { 94 | const two = 'two'; 95 | return two + s; 96 | } 97 | 98 | // composition: object.map(compose(f, g)) ≍ object.map(g).map(f) 99 | const fooBox = new ResultT('foo'); 100 | const isResultT = 101 | fooBox.map(() => two(one('foo'))).map(console.log) === 102 | fooBox 103 | .map(() => one('foo')) 104 | .map(two) 105 | .map(console.log); 106 | console.log('isResultT', isResultT); 107 | -------------------------------------------------------------------------------- /functional/promise/demo.ts: -------------------------------------------------------------------------------- 1 | 2 | interface Thenable { 3 | then(callback: Function): Thenable; 4 | catch(callback: Function): Thenable; 5 | finally(callback: Function): Thenable; 6 | } 7 | 8 | type Executor = (resolveFn: Function, rejectFn: Function) => void; 9 | 10 | enum states { 11 | pending = 'pending', 12 | resolved = 'resolved', 13 | rejected = 'rejected' 14 | } 15 | 16 | // Explanation: 17 | // The Promise is an elegant functional wrapper 18 | 19 | // How it works: (tried to simplify) 20 | 21 | // 1. User passes the exec function to the constructor - that will be invoked immediately on construction 22 | // 2. The exec function accepts 2 args which are callback functions ref of the Promise 23 | // 3. The user runs the resolve fn on success or the reject on error. 24 | // 4. Promise has a state machine - on construction it marked as "pending" 25 | // 5. When we use the Promise by calling "then" and passing our callback the Promise will store that callback in a callbacks list 26 | // 5. The user code runs, operates an async task and by the end of it the user will calls resolve (or error on failure) 27 | // 6. Once resolved - 28 | // // a. The Promise state will be marked as "resolved" 29 | // // b. The callback that was passed to the promisifiedOperation.then(cb) will then be executed 30 | 31 | export class MyPromise implements Thenable { 32 | private status: 'pending' | 'resolved' | 'rejected'; 33 | readonly thenCallbacks: Function[]; 34 | private catchCallback: Function = () => {}; 35 | private finallyCallback: Function = () => {}; 36 | private value: any; 37 | private error: Error | null = null; 38 | 39 | constructor(executor: Executor) { 40 | this.thenCallbacks = []; 41 | this.status = states.pending; 42 | executor((arg: any) => this.resolve(arg), (err: Error) => this.reject(err)); 43 | } 44 | 45 | then(callback: Function): MyPromise { 46 | if (this.status === states.resolved) { 47 | this.value = callback(this.value); 48 | } else { 49 | this.thenCallbacks.push(callback); 50 | } 51 | return this; 52 | } 53 | 54 | catch(callback: Function): MyPromise { 55 | if (this.status === 'rejected') { 56 | callback(this.error); 57 | } else { 58 | this.catchCallback = callback; 59 | } 60 | return this; 61 | } 62 | 63 | finally(callback: Function): MyPromise { 64 | if (this.status === states.resolved || this.status === states.rejected) { 65 | callback(this.value); 66 | } else { 67 | this.finallyCallback = callback; 68 | } 69 | return this; 70 | } 71 | 72 | private resolve(arg: any) { 73 | this.status = states.resolved; 74 | this.value = arg; 75 | console.log(this.thenCallbacks.length); 76 | for (const cb of this.thenCallbacks) { 77 | this.value = cb(this.value); 78 | } 79 | if (typeof this.finallyCallback !== 'undefined') { 80 | this.finallyCallback(this.value); 81 | } 82 | } 83 | 84 | private reject(arg: any) { 85 | this.status = states.rejected; 86 | this.error = arg; 87 | if (typeof this.catchCallback !== 'undefined') { 88 | this.catchCallback(this.error); 89 | } 90 | if (typeof this.finallyCallback !== 'undefined') { 91 | this.finallyCallback(this.value); 92 | } 93 | } 94 | 95 | // static resolve(){} 96 | // static reject(){} 97 | // static all(){} 98 | // static allSettled(){} 99 | // static race(){} 100 | } 101 | 102 | const promisified = () => { 103 | return new MyPromise((resolve, reject) => { 104 | setTimeout(() => resolve('inside timeout'), 1500) 105 | }) 106 | }; 107 | 108 | promisified() 109 | .then(() => { 110 | console.log('done!'); 111 | return promisified(); 112 | }) 113 | .then((result: any) => result) 114 | .then((res: any) => console.log(res)); 115 | 116 | -------------------------------------------------------------------------------- /swc/getFnNames.js: -------------------------------------------------------------------------------- 1 | // https://swc.rs/docs/usage/plugins#visitor-api 2 | 3 | const classAsTranspiledString = ` 4 | "use strict"; 5 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 6 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 7 | return new (P || (P = Promise))(function (resolve, reject) { 8 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 9 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 10 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 11 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 12 | }); 13 | }; 14 | var __generator = (this && this.__generator) || function (thisArg, body) { 15 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 16 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 17 | function verb(n) { return function (v) { return step([n, v]); }; } 18 | function step(op) { 19 | if (f) throw new TypeError("Generator is already executing."); 20 | while (_) try { 21 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 22 | if (y = 0, t) op = [op[0] & 2, t.value]; 23 | switch (op[0]) { 24 | case 0: case 1: t = op; break; 25 | case 4: _.label++; return { value: op[1], done: false }; 26 | case 5: _.label++; y = op[1]; op = [0]; continue; 27 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 28 | default: 29 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 30 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 31 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 32 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 33 | if (t[2]) _.ops.pop(); 34 | _.trys.pop(); continue; 35 | } 36 | op = body.call(thisArg, _); 37 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 38 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 39 | } 40 | }; 41 | exports.__esModule = true; 42 | exports.Foo = void 0; 43 | var Foo = /** @class */ (function () { 44 | function Foo() { 45 | this.dynamicMethod = function () { return "dynamicMethod"; }; 46 | } 47 | Foo.prototype.getBar = function () { 48 | this.dynamicMethodInFunction = function () { return "dynamicMethodInFunction"; }; 49 | return "bar"; 50 | }; 51 | Foo.prototype.concatStringWithNumber = function (sampleString, sampleNumber) { 52 | return sampleString + sampleNumber; 53 | }; 54 | Foo.prototype.convertNumberToString = function (value) { 55 | return value.toString(); 56 | }; 57 | Foo.prototype.getStringById = function (value) { 58 | return value.toString(); 59 | }; 60 | Foo.prototype.sumTwoNumbers = function (a, b) { 61 | return a + b; 62 | }; 63 | Foo.prototype.sampleMethodWithOptionalArgument = function (a, b) { 64 | return a + b; 65 | }; 66 | Foo.prototype.sampleMethodWithTwoOptionalArguments = function (a, b) { 67 | return a + b; 68 | }; 69 | Foo.prototype.sampleMethodReturningPromise = function (value) { 70 | return Promise.resolve(value); 71 | }; 72 | Foo.prototype.sampleMethodReturningVoidPromise = function (value) { 73 | return Promise.resolve(); 74 | }; 75 | Foo.prototype.sampleMethodReturningVoidPromiseWithoutParams = function () { 76 | return __awaiter(this, void 0, void 0, function () { 77 | return __generator(this, function (_a) { 78 | return [2 /*return*/, Promise.resolve()]; 79 | }); 80 | }); 81 | }; 82 | return Foo; 83 | }()); 84 | exports.Foo = Foo; 85 | 86 | `; 87 | 88 | const Visitor = require('@swc/core/Visitor.js').Visitor; 89 | const swc = require('@swc/core'); 90 | 91 | const names = []; 92 | class ExVisitor extends Visitor { 93 | visitFunction(n) { 94 | !!n.identifier?.value && names.push(n.identifier.value); 95 | return super.visitFunction(n); 96 | } 97 | visitAssignmentExpression(n) { 98 | if (n.right?.type === 'FunctionExpression') { 99 | n.left?.type === 'MemberExpression' && names.push(n.left.property.value); 100 | } 101 | return super.visitAssignmentExpression(n); 102 | } 103 | } 104 | 105 | class Transformer { 106 | static run() { 107 | swc.transformSync(classAsTranspiledString, { 108 | plugin: (program) => new ExVisitor().visitProgram(program), 109 | }); 110 | console.log('names', names); 111 | } 112 | } 113 | 114 | Transformer.run(); 115 | --------------------------------------------------------------------------------