Skip to content

Commit

Permalink
fix: service registry cannot register transpiled class (#3592)
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain authored Apr 26, 2024
1 parent a46c90a commit b3e24dc
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 15 deletions.
265 changes: 265 additions & 0 deletions packages/connection/__test__/common/rpc/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { ServiceRegistry, getServiceMethods } from '@opensumi/ide-connection/lib/common/rpc-service/registry';
import { Deferred } from '@opensumi/ide-core-common';

describe('registry should work', () => {
it('can register method properly', async () => {
const registry = new ServiceRegistry();

const updateDerfered = new Deferred<string[]>();

registry.onServicesUpdate((services) => {
updateDerfered.resolve(services);
});

const fnA = jest.fn();
registry.register('a', (...args) => {
fnA(...args);
});

registry.invoke('a');

expect(fnA).toHaveBeenCalledTimes(1);
expect(fnA).toHaveBeenCalledWith();

registry.invoke('a', 1, 2, 3);
expect(fnA).toHaveBeenCalledTimes(2);
expect(fnA).toHaveBeenCalledWith(1, 2, 3);

expect(await updateDerfered.promise).toEqual(['a']);
});

it('can register service properly', async () => {
const registry = new ServiceRegistry();

const simpleObj = {
method: jest.fn(),
__func: jest.fn(),
d: jest.fn(),
};

const updateDerfered = new Deferred<string[]>();

registry.onServicesUpdate((services) => {
updateDerfered.resolve(services);
});

registry.registerService(simpleObj);

registry.invoke('method', 1, 2, 3);

expect(simpleObj.method).toHaveBeenCalledTimes(1);
expect(simpleObj.method).toHaveBeenCalledWith(1, 2, 3);

expect(await updateDerfered.promise).toMatchInlineSnapshot(`
[
"__defineGetter__",
"__defineSetter__",
"__func",
"__lookupGetter__",
"__lookupSetter__",
"d",
"hasOwnProperty",
"isPrototypeOf",
"method",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
]
`);
});

it('can register service with name converter properly', async () => {
const registry = new ServiceRegistry();

const simpleObj = {
method: jest.fn(),
__func: jest.fn(),
d: jest.fn(),
};

const updateDerfered = new Deferred<string[]>();

registry.onServicesUpdate((services) => {
updateDerfered.resolve(services);
});

registry.registerService(simpleObj, {
nameConverter: (str) => str.toUpperCase(),
});

registry.invoke('METHOD', 1, 2, 3);

expect(simpleObj.method).toHaveBeenCalledTimes(1);
expect(simpleObj.method).toHaveBeenCalledWith(1, 2, 3);

expect(await updateDerfered.promise).toMatchInlineSnapshot(`
[
"__DEFINEGETTER__",
"__DEFINESETTER__",
"__FUNC",
"__LOOKUPGETTER__",
"__LOOKUPSETTER__",
"D",
"HASOWNPROPERTY",
"ISPROTOTYPEOF",
"METHOD",
"PROPERTYISENUMERABLE",
"TOLOCALESTRING",
"TOSTRING",
"VALUEOF",
]
`);
});

it('can get service methods properly', () => {
const methods = getServiceMethods(new A());
expect(methods).toMatchInlineSnapshot(`
[
"__defineGetter__",
"__defineSetter__",
"__func",
"__lookupGetter__",
"__lookupSetter__",
"d",
"hasOwnProperty",
"isPrototypeOf",
"method",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
]
`);

const transpiledA = createTranspiledA();
expect(getServiceMethods(transpiledA)).toMatchInlineSnapshot(`
[
"__defineGetter__",
"__defineSetter__",
"__func",
"__lookupGetter__",
"__lookupSetter__",
"d",
"hasOwnProperty",
"isPrototypeOf",
"method",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
]
`);

expect(getServiceMethods(simpleObj)).toMatchInlineSnapshot(`
[
"__defineGetter__",
"__defineSetter__",
"__func",
"__lookupGetter__",
"__lookupSetter__",
"d",
"hasOwnProperty",
"isPrototypeOf",
"method",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
]
`);
});
});

class A {
method() {}
__func() {}
d = () => {};
}

const simpleObj = {
method() {},
__func() {},
d: () => {},
};

/**
* Transpiled version of class A (use babel)
*/
function createTranspiledA() {
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
function _defineProperties(target, props) {
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < props.length; i++) {
const descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) {
descriptor.writable = true;
}
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) {
_defineProperties(Constructor.prototype, protoProps);
}
if (staticProps) {
_defineProperties(Constructor, staticProps);
}
Object.defineProperty(Constructor, 'prototype', { writable: false });
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
}
return obj;
}
function _toPropertyKey(t) {
const i = _toPrimitive(t, 'string');
return 'symbol' == typeof i ? i : i + '';
}
function _toPrimitive(t, r) {
if ('object' != typeof t || !t) {
return t;
}
const e = t[Symbol.toPrimitive];
if (void 0 !== e) {
const i = e.call(t, r || 'default');
if ('object' != typeof i) {
return i;
}
throw new TypeError('@@toPrimitive must return a primitive value.');
}
return ('string' === r ? String : Number)(t);
}
const A = /* #__PURE__*/ (function () {
function A() {
// @ts-expect-error: transpiled by babel
_classCallCheck(this, A);
// @ts-expect-error: transpiled by babel
_defineProperty(this, 'd', function () {});
}
// @ts-expect-error: transpiled by babel
return _createClass(A, [
{
key: 'method',
value: function method() {},
},
{
key: '__func',
value: function __func() {},
},
]);
})();

return new A();
}
35 changes: 20 additions & 15 deletions packages/connection/src/common/rpc-service/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,33 @@ import { Emitter } from '@opensumi/ide-core-common';
import { MessageIO, TSumiProtocol, TSumiProtocolMethod } from '../rpc';
import { RPCServiceMethod } from '../types';

const skipMethods = new Set(['constructor']);

export function getServiceMethods(service: any): string[] {
let props: any[] = [];

if (/^\s*class/.test(service.constructor.toString())) {
let obj = service;
do {
props = props.concat(Object.getOwnPropertyNames(obj));
} while ((obj = Object.getPrototypeOf(obj)));
props = props.sort().filter((e, i, arr) => e !== arr[i + 1] && typeof service[e] === 'function');
} else {
for (const prop in service) {
if (service[prop] && typeof service[prop] === 'function') {
props.push(prop);
const props = new Set<string>();

let obj = service;
do {
const propertyNames = Object.getOwnPropertyNames(obj);

for (const prop of propertyNames) {
if (skipMethods.has(prop)) {
continue;
}

if (typeof service[prop] === 'function') {
props.add(prop);
}
}
}
} while ((obj = Object.getPrototypeOf(obj)));

return props;
const array = Array.from(props);
array.sort();
return array;
}

/**
* Store all executable services
* Store all executable services, and provide a way to invoke them.
*/
export class ServiceRegistry {
protected emitter = new Emitter<string[]>();
Expand Down

1 comment on commit b3e24dc

@opensumi
Copy link
Contributor

@opensumi opensumi bot commented on b3e24dc Apr 26, 2024

Choose a reason for hiding this comment

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

Release Candidate Summary:

Released 🚀 2.27.3-rc-1714116491.0

2.27.3-rc-1714116491.0

user input ref: main

b3e24dc fix: service registry cannot register transpiled class (#3592)

Please sign in to comment.