Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
linhntaim committed Nov 6, 2023
2 parents a5d0536 + 9c11cd8 commit 5f22e6c
Show file tree
Hide file tree
Showing 9 changed files with 511 additions and 189 deletions.
8 changes: 6 additions & 2 deletions .coveralls.yml.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
service_name: travis-ci
service_name: $CI_NAME
service_job_id: $CI_JOB_ID
service_build_url: $CI_BUILD_URL
service_branch: $CI_BRANCH
service_pull_request: $CI_PULL_REQUEST

repo_token:
repo_token: $COVERALLS_REPO_TOKEN
33 changes: 33 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: build
run-name: ${{ github.actor }} is making a build
on:
push:
branches:
- master
jobs:
test-coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Information
run: |
echo "Node.js"
node -v
echo "npm"
npm -v
- name: Install
run: npm ci
- name: Test
run: |
npm test
mkdir coverage
npm run test:report > coverage/test.lcov
- name: Publish test result to Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ lerna-debug.log*
*.sw?

# Test results
.coveralls.yml
.nyc_output
.coveralls.yml
coverage

# Common
Expand All @@ -43,5 +43,6 @@ babel.config.*
nyc.config.js
.coveralls.yml.example
.travis.yml
.github
/tests
/src
192 changes: 189 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# magic-class

[![NPM version](https://img.shields.io/npm/v/magic-class.svg?style=flat-square)](https://www.npmjs.com/package/magic-class)
[![Travis (.org)](https://img.shields.io/travis/com/linhntaim/magic-class?style=flat-square)](https://app.travis-ci.com/github/linhntaim/magic-class)
[![Coveralls github](https://img.shields.io/coveralls/github/linhntaim/magic-class?style=flat-square)](https://coveralls.io/github/linhntaim/magic-class)
[![NPM](https://img.shields.io/npm/l/magic-class?style=flat-square)](https://github.com/linhntaim/magic-class/blob/master/LICENSE)
[![Github Actions](https://img.shields.io/github/actions/workflow/status/linhntaim/magic-class/build.yml?style=flat-square)](https://github.com/linhntaim/magic-class/actions/workflows/build.yml)
[![Coveralls](https://img.shields.io/coveralls/github/linhntaim/magic-class?style=flat-square)](https://coveralls.io/github/linhntaim/magic-class)
[![License](https://img.shields.io/npm/l/magic-class?style=flat-square)](https://github.com/linhntaim/magic-class/blob/master/LICENSE)

Activate PHP-like magic methods in Javascript classes and instances.

Expand All @@ -27,6 +27,7 @@ Activate PHP-like magic methods in Javascript classes and instances.
- [Static `__has`](#static-__has)
- [Static `__delete`](#static-__delete)
- [Static method chaining](#static-method-chaining)
- [Use `Symbol` as magic method name](#use-symbol-as-magic-method-name)
- [Prototype operations](#prototype-operations)
- [Strict mode](#strict-mode)
- [Special magic static methods](#special-magic-static-methods)
Expand Down Expand Up @@ -71,6 +72,14 @@ class NormalClass
return {method: `static:${method}`, parameters}
}

static __has(prop) {
return `static:${prop}` in this.magicProps
}

static __delete(prop) {
return delete this.magicProps[`static:${prop}`]
}

magicProps = {}

constructor(normal) {
Expand All @@ -95,6 +104,14 @@ class NormalClass
return {method, parameters}
}

__has(prop) {
return prop in this.magicProps
}

__delete(prop) {
return delete this.magicProps[prop]
}

__invoke(...parameters) {
return {parameters}
}
Expand All @@ -110,6 +127,11 @@ console.log(MagicClass.magic) // (boolean) true
console.log(MagicClass.any) // (string) 'static:any'
// magic static __call
console.log(MagicClass.callAny(true)) // (object) {method: 'static:callAny', parameters: [true]}
// magic static __has
console.log('magic' in MagicClass) // (boolean) true
// magic static __delete
console.log(delete MagicClass.magic) // (boolean) true
console.log('magic' in MagicClass) // (boolean) false

// Create magic instance
const magicInstance = new MagicClass('normal')
Expand All @@ -123,6 +145,11 @@ console.log(magicInstance.magic) // (boolean) true
console.log(magicInstance.any) // (string) 'any'
// magic __call
console.log(magicInstance.callAny(true)) // (object) {method: 'callAny', parameters: [true]}
// magic __has
console.log('magic' in magicInstance) // (boolean) true
// magic __delete
console.log(delete magicInstance.magic) // (boolean) true
console.log('magic' in magicInstance) // (boolean) false
// magic __invoke
console.log(magicInstance(true)) // (object) {parameters: [true]}
```
Expand Down Expand Up @@ -733,6 +760,162 @@ const magicChain = MagicClass.push(1).callInsert(2).callAdd(3).callAny(4).any.se
console.log(magicChain) // (array) [0, 1, 'callInsert:2', 'callAdd:3', 'call:callAny', 'get:any']
```

### Use `Symbol` as magic method name

Besides strings, there are defined symbols you can use to naming the magic methods:

```javascript
const magic = require('magic-class')
/* or ES6 */
// import magic from 'magic-class'

// Defined symbols
console.log(magic.__set) // (symbol) Symbol(Symbol.__set)
console.log(magic.__get) // (symbol) Symbol(Symbol.__get)
console.log(magic.__call) // (symbol) Symbol(Symbol.__call)
console.log(magic.__has) // (symbol) Symbol(Symbol.__has)
console.log(magic.__delete) // (symbol) Symbol(Symbol.__delete)
console.log(magic.__invoke) // (symbol) Symbol(Symbol.__invoke)

class NormalClass
{
static magicProps = {}

// equivalent to `static __set`
static [magic.__set](prop, value) {
this.magicProps[`static:${prop}`] = value
}

// equivalent to `static __get`
static [magic.__get](prop) {
if (`static:${prop}` in this.magicProps) {
return this.magicProps[`static:${prop}`]
}
if (prop.startsWith('call')) {
return undefined
}
return `static:${prop}`
}

// equivalent to `static __call`
static [magic.__call](method, ...parameters) {
return {method: `static:${method}`, parameters}
}

// equivalent to `static __has`
static [magic.__has](prop) {
return `static:${prop}` in this.magicProps
}

// equivalent to `static __delete`
static [magic.__delete](prop) {
return delete this.magicProps[`static:${prop}`]
}

magicProps = {}

constructor(normal) {
this.normal = normal
}

// equivalent to `__set`
[magic.__set](prop, value) {
this.magicProps[prop] = value
}

// equivalent to `__get`
[magic.__get](prop) {
if (prop in this.magicProps) {
return this.magicProps[prop]
}
if (prop.startsWith('call')) {
return undefined
}
return prop
}

// equivalent to `__call`
[magic.__call](method, ...parameters) {
return {method, parameters}
}

// equivalent to `__has`
[magic.__has](prop) {
return prop in this.magicProps
}

// equivalent to `__delete`
[magic.__delete](prop) {
return delete this.magicProps[prop]
}

// equivalent to `__invoke`
[magic.__invoke](...parameters) {
return {parameters}
}
}

// Create magic class
const MagicClass = magic(NormalClass)
// magic static __set
MagicClass.magic = true
console.log(MagicClass.magicProps) // (object) {'static:magic': true}
// magic static __get
console.log(MagicClass.magic) // (boolean) true
console.log(MagicClass.any) // (string) 'static:any'
// magic static __call
console.log(MagicClass.callAny(true)) // (object) {method: 'static:callAny', parameters: [true]}
// magic static __has
console.log('magic' in MagicClass) // (boolean) true
// magic static __delete
console.log(delete MagicClass.magic) // (boolean) true
console.log('magic' in MagicClass) // (boolean) false

// Create magic instance
const magicInstance = new MagicClass('normal')
/* or */
// const magicInstance = magic(new NormalClass())
// magic __set
magicInstance.magic = true
console.log(magicInstance.magicProps) // (object) {magic: true}
// magic __get
console.log(magicInstance.magic) // (boolean) true
console.log(magicInstance.any) // (string) 'any'
// magic __call
console.log(magicInstance.callAny(true)) // (object) {method: 'callAny', parameters: [true]}
// magic __has
console.log('magic' in magicInstance) // (boolean) true
// magic __delete
console.log(delete magicInstance.magic) // (boolean) true
console.log('magic' in magicInstance) // (boolean) false
// magic __invoke
console.log(magicInstance(true)) // (object) {parameters: [true]}
```

***Note*: The `Symbol`-naming magic method has a higher priority in calling
than the `string`-naming one.

```javascript
const magic = require('magic-class')
/* or ES6 */
// import magic from 'magic-class'

class NormalClass
{
// equivalent to `__get` but has a higher priority
[magic.__get](prop) {
return `symbol:${prop}`
}

__get(prop) {
return prop
}
}

const magicInstance = magic(new NormalClass())
console.log(magicInstance.magic) // (string) 'symbol:magic'
```

### Prototype operations

Technically, **the class after the magic is activated (which is a proxy object)** is different from
Expand Down Expand Up @@ -795,6 +978,9 @@ console.log(magicInstance instanceof GrandParentClass) // (boolean) tru
console.log(magicInstance.value) // (string) 'value'
```

***Note*: Operation by `setPrototypeOf` method is not allowed. Trying to apply it
to magic classes or instances will throw `TypeError` exception.

### Strict mode

Strict mode is on by default while activating the magic. It will raise exceptions in following cases:
Expand Down
Loading

0 comments on commit 5f22e6c

Please sign in to comment.