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

chore(firestore): deprecation warnings for v8 API ahead of future major release #8169

Merged
merged 30 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
348808e
chore(firestore): deprecation warning for v8 API ahead of future majo…
russellwheatley Nov 20, 2024
d8e1fc4
Merge branch 'v8-deprecations' into firestore-deprecations
russellwheatley Nov 22, 2024
736c07b
proof of concept
russellwheatley Nov 29, 2024
15f4baf
fix mock resolve value
russellwheatley Nov 29, 2024
47317b4
tests
russellwheatley Nov 29, 2024
3d9aa5e
update common functions
russellwheatley Nov 29, 2024
38b018b
remove warnings
russellwheatley Nov 29, 2024
d1ad280
tweak test name
russellwheatley Nov 29, 2024
4a5c5ea
query unit tests
russellwheatley Nov 29, 2024
21302d0
firestore unit tests
russellwheatley Nov 29, 2024
3c728e2
refactor how to get namespace
russellwheatley Nov 29, 2024
da7d510
FieldValue tests
russellwheatley Nov 29, 2024
0d752d3
rm code comments
russellwheatley Dec 6, 2024
b423756
FirestoreFilter
russellwheatley Dec 6, 2024
3658b6c
where query
russellwheatley Dec 6, 2024
323143c
update default firestore
russellwheatley Dec 12, 2024
af75b13
fix broken firestore tests
russellwheatley Dec 12, 2024
b7180da
more firestore method deprecated
russellwheatley Dec 12, 2024
6562557
add connectFirestoreEmulator + few more deprecations
russellwheatley Dec 12, 2024
c670dcf
more firestore.* api deprecations
russellwheatley Dec 12, 2024
fbf42c0
further firestore.* deprecations
russellwheatley Dec 12, 2024
4467e54
FirestorePersistentCacheIndexManager.* deprecations
russellwheatley Dec 12, 2024
9b451d4
refactor deprecation warning. working
russellwheatley Dec 13, 2024
7dfec74
Timestamp proxy
russellwheatley Dec 17, 2024
071420c
chore: tidy up
russellwheatley Dec 17, 2024
02433f3
chore: remove unused args
russellwheatley Dec 17, 2024
f253290
chore: update statics logic
russellwheatley Dec 18, 2024
61e782c
fix: proxy behavior
russellwheatley Dec 18, 2024
e3064a3
fix: broken e2e tests
russellwheatley Dec 18, 2024
c79ff51
Apply suggestions from code review
russellwheatley Dec 20, 2024
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
6 changes: 2 additions & 4 deletions packages/app/lib/FirebaseApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ export default class FirebaseApp {
}

extendApp(extendedProps) {
// this method has no modular alternative, send true for param 'noAlternative'
warnIfNotModularCall(arguments, '', true);
warnIfNotModularCall(arguments);
this._checkDestroyed();
Object.assign(this, extendedProps);
}
Expand All @@ -74,8 +73,7 @@ export default class FirebaseApp {
}

toString() {
// this method has no modular alternative, send true for param 'noAlternative'
warnIfNotModularCall(arguments, '', true);
warnIfNotModularCall(arguments);
return this.name;
}
}
224 changes: 199 additions & 25 deletions packages/app/lib/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,50 +108,135 @@

const mapOfDeprecationReplacements = {
crashlytics: {
checkForUnsentReports: 'checkForUnsentReports()',
crash: 'crash()',
deleteUnsentReports: 'deleteUnsentReports()',
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
log: 'log()',
setAttribute: 'setAttribute()',
setAttributes: 'setAttributes()',
setUserId: 'setUserId()',
recordError: 'recordError()',
sendUnsentReports: 'sendUnsentReports()',
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
default: {
checkForUnsentReports: 'checkForUnsentReports()',
crash: 'crash()',
deleteUnsentReports: 'deleteUnsentReports()',
didCrashOnPreviousExecution: 'didCrashOnPreviousExecution()',
log: 'log()',
setAttribute: 'setAttribute()',
setAttributes: 'setAttributes()',
setUserId: 'setUserId()',
recordError: 'recordError()',
sendUnsentReports: 'sendUnsentReports()',
setCrashlyticsCollectionEnabled: 'setCrashlyticsCollectionEnabled()',
},
},
firestore: {
default: {
batch: 'writeBatch()',
loadBundle: 'loadBundle()',
namedQuery: 'namedQuery()',
clearPersistence: 'clearIndexedDbPersistence()',
waitForPendingWrites: 'waitForPendingWrites()',
terminate: 'terminate()',
useEmulator: 'connectFirestoreEmulator()',
collection: 'collection()',
collectionGroup: 'collectionGroup()',
disableNetwork: 'disableNetwork()',
doc: 'doc()',
enableNetwork: 'enableNetwork()',
runTransaction: 'runTransaction()',
settings: 'settings()',
persistentCacheIndexManager: 'getPersistentCacheIndexManager()',
},
statics: {
setLogLevel: 'setLogLevel()',
Filter: 'where()',
FieldValue: 'FieldValue',
Timestamp: 'Timestamp',
GeoPoint: 'GeoPoint',
Blob: 'Bytes',
FieldPath: 'FieldPath',
},
FirestoreCollectionReference: {
count: 'getCountFromServer()',
countFromServer: 'getCountFromServer()',
endAt: 'endAt()',
endBefore: 'endBefore()',
get: 'getDocs()',
isEqual: NO_REPLACEMENT,
limit: 'limit()',
limitToLast: 'limitToLast()',
onSnapshot: 'onSnapshot()',
orderBy: 'orderBy()',
startAfter: 'startAfter()',
startAt: 'startAt()',
where: 'where()',
add: 'addDoc()',
doc: 'doc()',
},
FirestoreDocumentReference: {
collection: 'collection()',
delete: 'deleteDoc()',
get: 'getDoc()',
isEqual: NO_REPLACEMENT,
onSnapshot: 'onSnapshot()',
set: 'setDoc()',
update: 'updateDoc()',
},
FirestoreDocumentSnapshot: {
isEqual: NO_REPLACEMENT,
},
FirestoreFieldValue: {
arrayRemove: 'arrayRemove()',
arrayUnion: 'arrayUnion()',
delete: 'deleteField()',
increment: 'increment()',
serverTimestamp: 'serverTimestamp()',
},
Filter: {
or: 'or()',
and: 'and()',
},
FirestorePersistentCacheIndexManager: {
enableIndexAutoCreation: 'enablePersistentCacheIndexAutoCreation()',
disableIndexAutoCreation: 'disablePersistentCacheIndexAutoCreation()',
deleteAllIndexes: 'deleteAllPersistentCacheIndexes()',
},
FirestoreTimestamp: {
seconds: NO_REPLACEMENT,
nanoseconds: NO_REPLACEMENT,
},
},
};

const v8deprecationMessage =
'This v8 method is deprecated and will be removed in the next major release ' +
'as part of move to match Firebase Web modular v9 SDK API.';

export function deprecationConsoleWarning(moduleName, methodName, isModularMethod) {
export function deprecationConsoleWarning(nameSpace, methodName, instanceName, isModularMethod) {
if (!isModularMethod) {
const moduleMap = mapOfDeprecationReplacements[moduleName];
const moduleMap = mapOfDeprecationReplacements[nameSpace];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
// only warn if it is mapped and purposefully deprecated
if (replacementMethodName) {
const message = createMessage(moduleName, methodName);

const instanceMap = moduleMap[instanceName];
const deprecatedMethod = instanceMap[methodName];
if (instanceMap && deprecatedMethod) {
const message = createMessage(nameSpace, methodName, instanceName);
// eslint-disable-next-line no-console
console.warn(message);
}
}
}
}

export function createMessage(moduleName, methodName, uniqueMessage = '') {
if (uniqueMessage.length > 0) {
export function createMessage(
nameSpace,
methodName,
instanceName = 'default',

Check warning on line 226 in packages/app/lib/common/index.js

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/common/index.js#L226

Added line #L226 was not covered by tests
uniqueMessage = null,
) {
if (uniqueMessage) {
// Unique deprecation message used for testing
return uniqueMessage;
}

const moduleMap = mapOfDeprecationReplacements[moduleName];
const moduleMap = mapOfDeprecationReplacements[nameSpace];
if (moduleMap) {
const replacementMethodName = moduleMap[methodName];
if (replacementMethodName) {
const instance = moduleMap[instanceName];
if (instance) {
const replacementMethodName = instance[methodName];

if (replacementMethodName !== NO_REPLACEMENT) {
return v8deprecationMessage + ` Please use \`${replacementMethodName}\` instead.`;
} else {
Expand All @@ -161,9 +246,98 @@
}
}

function getNamespace(target) {
if (target.GeoPoint) {
// target is statics object. GeoPoint is a static class on Firestore
return 'firestore';
}
if (target._config && target._config.namespace) {
return target._config.namespace;
}
const className = target.name ? target.name : target.constructor.name;
return Object.keys(mapOfDeprecationReplacements).find(key => {
if (mapOfDeprecationReplacements[key][className]) {
return key;
}
});
}

function getInstanceName(target) {
if (target.GeoPoint) {
// target is statics object. GeoPoint is a static class on Firestore
return 'statics';
}
if (target._config) {
// module class instance, we use default to store map of deprecated methods
return 'default';
}
if (target.name) {
// It's a function which has a name property unlike classes
return target.name;
}
// It's a class instance
return target.constructor.name;
}

export function createDeprecationProxy(instance) {
russellwheatley marked this conversation as resolved.
Show resolved Hide resolved
return new Proxy(instance, {
construct(target, args) {
// needed for Timestamp which we pass as static, when we construct new instance, we need to wrap it in proxy again.
return createDeprecationProxy(new target(...args));
},
get(target, prop, receiver) {
const originalMethod = target[prop];

if (prop === 'constructor') {
return Reflect.get(target, prop, receiver);
}

if (target && target.constructor && target.constructor.name === 'FirestoreTimestamp') {
deprecationConsoleWarning('firestore', prop, 'FirestoreTimestamp', false);
return Reflect.get(target, prop, receiver);
}

if (target && target.name === 'firebaseModuleWithApp') {
// statics
if (
prop === 'Filter' ||
prop === 'FieldValue' ||
prop === 'Timestamp' ||
prop === 'GeoPoint' ||
prop === 'Blob' ||
prop === 'FieldPath'
) {
deprecationConsoleWarning('firestore', prop, 'statics', false);
}
if (prop !== 'setLogLevel') {
// we want to capture setLogLevel function call which we do below
return Reflect.get(target, prop, receiver);
}
}

if (typeof originalMethod === 'function') {
return function (...args) {
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
const instanceName = getInstanceName(target);
const nameSpace = getNamespace(target);

deprecationConsoleWarning(nameSpace, prop, instanceName, isModularMethod);

return originalMethod.apply(target, filterModularArgument(args));
};
}
return Reflect.get(target, prop, receiver);
},
});
}

export const MODULAR_DEPRECATION_ARG = 'react-native-firebase-modular-method-call';

export function warnIfNotModularCall(args, replacementMethodName, noAlternative) {
export function filterModularArgument(list) {
return list.filter(arg => arg !== MODULAR_DEPRECATION_ARG);
}

export function warnIfNotModularCall(args, replacementMethodName = '') {
for (let i = 0; i < args.length; i++) {
if (args[i] === MODULAR_DEPRECATION_ARG) {
return;
Expand All @@ -173,7 +347,7 @@
'This v8 method is deprecated and will be removed in the next major release ' +
'as part of move to match Firebase Web modular v9 SDK API.';

if (!noAlternative) {
if (replacementMethodName.length > 0) {
message += ` Please use \`${replacementMethodName}\` instead.`;
}

Expand Down
16 changes: 10 additions & 6 deletions packages/app/lib/common/unitTestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,29 @@ export const checkV9Deprecation = (modularFunction: () => void, nonModularFuncti
export type CheckV9DeprecationFunction = (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
methodNameKey: string,
uniqueMessage: string = '',
) => void;

export const createCheckV9Deprecation = (moduleName: string): CheckV9DeprecationFunction => {
export const createCheckV9Deprecation = (moduleNames: string[]): CheckV9DeprecationFunction => {
return (
modularFunction: () => void,
nonModularFunction: () => void,
methodName: string,
uniqueMessage = '',
methodNameKey: string,
uniqueMessage: string?,
) => {
const moduleName = moduleNames[0]; // firestore, database, etc
const instanceName = moduleNames[1] || 'default'; // default, FirestoreCollectionReference, etc
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
// Do not call `mockRestore()` as it removes the spy
consoleWarnSpy.mockReset();
modularFunction();
expect(consoleWarnSpy).not.toHaveBeenCalled();
consoleWarnSpy.mockReset();
consoleWarnSpy.mockRestore();
const consoleWarnSpy2 = jest.spyOn(console, 'warn').mockImplementation(warnMessage => {
const message = createMessage(moduleName, methodName, uniqueMessage);
expect(message).toMatch(warnMessage);
const message = createMessage(moduleName, methodNameKey, instanceName, uniqueMessage);
expect(warnMessage).toMatch(message);
});
nonModularFunction();

Expand Down
37 changes: 8 additions & 29 deletions packages/app/lib/internal/registry/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*
*/

import { isString, MODULAR_DEPRECATION_ARG, deprecationConsoleWarning } from '../../common';
import { isString, createDeprecationProxy } from '../../common';
import FirebaseApp from '../../FirebaseApp';
import SDK_VERSION from '../../version';
import { DEFAULT_APP_NAME, KNOWN_NAMESPACES } from '../constants';
Expand Down Expand Up @@ -114,11 +114,11 @@ function getOrCreateModuleForApp(app, moduleNamespace) {
}

if (!APP_MODULE_INSTANCE[app.name][key]) {
APP_MODULE_INSTANCE[app.name][key] = new ModuleClass(
app,
NAMESPACE_REGISTRY[moduleNamespace],
customUrlOrRegionOrDatabaseId,
const module = createDeprecationProxy(
new ModuleClass(app, NAMESPACE_REGISTRY[moduleNamespace], customUrlOrRegionOrDatabaseId),
);

APP_MODULE_INSTANCE[app.name][key] = module;
}

return APP_MODULE_INSTANCE[app.name][key];
Expand Down Expand Up @@ -180,8 +180,9 @@ function getOrCreateModuleForRoot(moduleNamespace) {
}

Object.assign(firebaseModuleWithApp, statics || {});
Object.freeze(firebaseModuleWithApp);
MODULE_GETTER_FOR_ROOT[moduleNamespace] = firebaseModuleWithApp;
// Object.freeze(firebaseModuleWithApp);
// Wrap around statics, e.g. firebase.firestore.FieldValue, removed freeze as it stops proxy working. it is deprecated anyway
MODULE_GETTER_FOR_ROOT[moduleNamespace] = createDeprecationProxy(firebaseModuleWithApp);

return MODULE_GETTER_FOR_ROOT[moduleNamespace];
}
Expand Down Expand Up @@ -277,28 +278,6 @@ export function getFirebaseRoot() {
return createFirebaseRoot();
}

function createDeprecationProxy(instance) {
return new Proxy(instance, {
get(target, prop, receiver) {
const originalMethod = target[prop];
if (prop === 'constructor') {
return target.constructor;
}
if (typeof originalMethod === 'function') {
return function (...args) {
const isModularMethod = args.includes(MODULAR_DEPRECATION_ARG);
const moduleName = receiver._config.namespace;

deprecationConsoleWarning(moduleName, prop, isModularMethod);

return originalMethod.apply(target, args);
};
}
return Reflect.get(target, prop, receiver);
},
});
}

/**
*
* @param options
Expand Down
2 changes: 1 addition & 1 deletion packages/crashlytics/__tests__/crashlytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Crashlytics', function () {
let checkV9Deprecation: CheckV9DeprecationFunction;

beforeEach(function () {
checkV9Deprecation = createCheckV9Deprecation('crashlytics');
checkV9Deprecation = createCheckV9Deprecation(['crashlytics']);

// @ts-ignore test
jest.spyOn(FirebaseModule.prototype, 'native', 'get').mockImplementation(() => {
Expand Down
Loading
Loading