Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Showcase: patching sinon to work with swc #1

Open
wants to merge 1 commit into
base: go-back-to-15-2-0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/sinon/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const valueToString = require("@sinonjs/commons").valueToString;
const fakeServer = require("nise").fakeServer;
const fakeXhr = require("nise").fakeXhr;
const usePromiseLibrary = require("./util/core/use-promise-library");
const isESModuleGetter = require("./util/core/is-es-module-getter");

const DEFAULT_LEAK_THRESHOLD = 10000;

Expand Down Expand Up @@ -255,6 +256,10 @@ function Sandbox() {
}

if (typeof descriptor.get === "function") {
if (isESModuleGetter(object, property)) {
sandbox.replaceGetter(object, property, () => replacement);
return replacement;
}
throw new Error("Use sandbox.replaceGetter for replacing getters");
}

Expand Down Expand Up @@ -382,11 +387,25 @@ function Sandbox() {

sandbox.spy = function spy() {
const createdSpy = sinonSpy.apply(sinonSpy, arguments);
const [object, property] = arguments;
if (isESModuleGetter(object, property)) {
Object.defineProperty(object, property, {
get: () => createdSpy,
configurable: true,
});
}
return commonPostInitSetup(arguments, createdSpy);
};

sandbox.stub = function stub() {
const createdStub = sinonStub.apply(sinonStub, arguments);
const [object, property] = arguments;
if (isESModuleGetter(object, property)) {
Object.defineProperty(object, property, {
get: () => createdStub,
configurable: true,
});
}
return commonPostInitSetup(arguments, createdStub);
};

Expand Down
21 changes: 16 additions & 5 deletions lib/sinon/stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const wrapMethod = require("./util/core/wrap-method");
const throwOnFalsyObject = require("./throw-on-falsy-object");
const valueToString = require("@sinonjs/commons").valueToString;
const walkObject = require("./util/core/walk-object");
const isESModuleGetter = require("./util/core/is-es-module-getter");

const forEach = arrayProto.forEach;
const pop = arrayProto.pop;
Expand Down Expand Up @@ -88,16 +89,19 @@ function stub(object, property) {

assertValidPropertyDescriptor(actualDescriptor, property);

const isGetter = isESModuleGetter(object, property);
const isObjectOrFunction =
typeof object === "object" || typeof object === "function";
const isStubbingEntireObject =
typeof property === "undefined" && isObjectOrFunction;
const isCreatingNewStub = !object && typeof property === "undefined";
const isNonFuncGetter = isGetter && typeof actualDescriptor.get() !== "function";
const isStubbingNonFuncProperty =
isObjectOrFunction &&
typeof property !== "undefined" &&
(typeof actualDescriptor === "undefined" ||
typeof actualDescriptor.value !== "function");
isNonFuncGetter ||
(!isGetter && typeof actualDescriptor.value !== "function"));

if (isStubbingEntireObject) {
return walkObject(stub, object);
Expand All @@ -107,10 +111,7 @@ function stub(object, property) {
return createStub();
}

const func =
typeof actualDescriptor.value === "function"
? actualDescriptor.value
: null;
const func = getFuncToStub(actualDescriptor, isGetter);
const s = createStub(func);

extend.nonEnum(s, {
Expand All @@ -128,6 +129,16 @@ function stub(object, property) {
});

return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s);

function getFuncToStub(descriptor, isGetter) {
if (typeof descriptor.value === "function") {
return descriptor.value;
}
if (isGetter) {
return descriptor.get();
}
return null;
}
}

function assertValidPropertyDescriptor(descriptor, property) {
Expand Down
24 changes: 24 additions & 0 deletions lib/sinon/util/core/is-es-module-getter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use-strict";

const getPropertyDescriptor = require("./get-property-descriptor");

/**
* Returns `true` if the object is an ES Module and the property is a getter.
* This is used to support exports as generated by `swc`.
*
* @param object the object to examine
* @param property the property to examine
*
* @returns {boolean}
*/

module.exports = function isESModuleGetter(object, property) {
Copy link

Choose a reason for hiding this comment

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

This is not really a true name: it's more like isTranspiledEsModuleGetter 😄

const descriptor = getPropertyDescriptor(object, property);
return (
object &&
// eslint-disable-next-line no-underscore-dangle
object.__esModule &&
Copy link

Choose a reason for hiding this comment

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

Would you happen to know if __esModule is more or less a standard shared between the various transpilers (Webpack, SWC, etc)?

descriptor &&
typeof descriptor.get === "function"
);
};
19 changes: 15 additions & 4 deletions lib/sinon/util/core/wrap-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const noop = () => {};
const getPropertyDescriptor = require("./get-property-descriptor");
const extend = require("./extend");
const sinonType = require("./sinon-type");
const isESModuleGetter = require("./is-es-module-getter");
const hasOwnProperty =
require("@sinonjs/commons").prototypes.object.hasOwnProperty;
const valueToString = require("@sinonjs/commons").valueToString;
Expand Down Expand Up @@ -88,6 +89,8 @@ module.exports = function wrapMethod(object, property, method) {

let error, wrappedMethod, i, wrappedMethodDesc, target, accessor;

const isGetter = isESModuleGetter(object, property);

const wrappedMethods = [];

function simplePropertyAssignment() {
Expand All @@ -102,9 +105,15 @@ module.exports = function wrapMethod(object, property, method) {
? object.hasOwnProperty(property) // eslint-disable-line @sinonjs/no-prototype-methods/no-prototype-methods
: hasOwnProperty(object, property);

function makeNewMethodDescriptor() {
if (typeof method === "function") {
return isGetter ? { get: () => method } : { value: method };
}
return method;
}

if (hasES5Support) {
const methodDesc =
typeof method === "function" ? { value: method } : method;
const methodDesc = makeNewMethodDescriptor();
wrappedMethodDesc = getPropertyDescriptor(object, property);

if (!wrappedMethodDesc) {
Expand Down Expand Up @@ -163,10 +172,12 @@ module.exports = function wrapMethod(object, property, method) {
function extendObjectWithWrappedMethods() {
for (i = 0; i < wrappedMethods.length; i++) {
accessor = getAccessor(object, property, wrappedMethods[i]);
target = accessor ? method[accessor] : method;
target = accessor && !isGetter ? method[accessor] : method;
extend.nonEnum(target, {
displayName: property,
wrappedMethod: wrappedMethods[i],
wrappedMethod: isGetter
? wrappedMethods[i]()
: wrappedMethods[i],

// Set up an Error object for a stack trace which can be used later to find what line of
// code the original method was created on.
Expand Down