Skip to content

Commit 56cbbfd

Browse files
author
Michael Lehenbauer
authored
Enable strictPropertyInitialization for Firestore (#2078)
* Enable strictPropertyInitialization: Refactor Path to BasePath<T> similar to Android. This avoids having uninitialized properties due to the init() method doing the construction. * Remove dummy FirebaseConfig class. This class doesn't exist on other platforms and was responsible for a bunch of strict-property-initialization violations. * Initialize _dataConverter in constructor. Fixes a strict-property-initialization violation. * Fix or suppress uninitialized properties in IndexedDbPersistence. * Fix StreamBridge property types to correctly indicate they can be undefined. * Make sure abstract class members are marked abstract so they don't need to be initialized. * FieldValue.typeOrder * Mutation.type / key / precondition * PersistenceTransaction.currentSequenceNumber * Fix uninitialized LLRBEmptyNode properties. Throw errors instead of potentially returning undefined. I ran the SortedMap perf tests before/after this change and there was no discernible difference. * Fix uninitialized properties in MemoryPersistence. * Fix misc. uninitialized properties. * Suppress some intentionally uninitialized properties. These would be obnoxious to define as `| undefined` but we also can't initialize them in the constructor. * Fix some misc. uninitialized properties in tests. * Suppress some intentionally uninitialized properties in tests. * Enable strictPropertyInitialization. * Minor CR feedback.
1 parent a20dbea commit 56cbbfd

28 files changed

+184
-158
lines changed

packages/firestore/src/api/credentials.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class FirebaseCredentialsProvider implements CredentialsProvider {
134134
private tokenListener: ((token: string | null) => void) | null = null;
135135

136136
/** Tracks the current User. */
137-
private currentUser: User;
137+
private currentUser: User = User.UNAUTHENTICATED;
138138

139139
/**
140140
* Counter used to detect if the token changed while a getToken request was

packages/firestore/src/api/database.ts

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -280,24 +280,18 @@ class FirestoreSettings {
280280
}
281281
}
282282

283-
class FirestoreConfig {
284-
databaseId: DatabaseId;
285-
persistenceKey: string;
286-
credentials: CredentialsProvider;
287-
firebaseApp: FirebaseApp;
288-
settings: FirestoreSettings;
289-
persistence: boolean;
290-
}
291-
292283
/**
293284
* The root reference to the database.
294285
*/
295286
export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
296287
// The objects that are a part of this API are exposed to third-parties as
297288
// compiled javascript so we want to flag our private members with a leading
298289
// underscore to discourage their use.
299-
private readonly _config: FirestoreConfig;
300290
readonly _databaseId: DatabaseId;
291+
private readonly _persistenceKey: string;
292+
private _credentials: CredentialsProvider;
293+
private readonly _firebaseApp: FirebaseApp | null = null;
294+
private _settings: FirestoreSettings;
301295

302296
// The firestore client instance. This will be available as soon as
303297
// configureClient is called, but any calls against it will block until
@@ -311,18 +305,17 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
311305
// TODO(mikelehen): Use modularized initialization instead.
312306
readonly _queue = new AsyncQueue();
313307

314-
_dataConverter: UserDataConverter;
308+
readonly _dataConverter: UserDataConverter;
315309

316310
constructor(databaseIdOrApp: FirestoreDatabase | FirebaseApp) {
317-
const config = new FirestoreConfig();
318311
if (typeof (databaseIdOrApp as FirebaseApp).options === 'object') {
319312
// This is very likely a Firebase app object
320313
// TODO(b/34177605): Can we somehow use instanceof?
321314
const app = databaseIdOrApp as FirebaseApp;
322-
config.firebaseApp = app;
323-
config.databaseId = Firestore.databaseIdFromApp(app);
324-
config.persistenceKey = config.firebaseApp.name;
325-
config.credentials = new FirebaseCredentialsProvider(app);
315+
this._firebaseApp = app;
316+
this._databaseId = Firestore.databaseIdFromApp(app);
317+
this._persistenceKey = app.name;
318+
this._credentials = new FirebaseCredentialsProvider(app);
326319
} else {
327320
const external = databaseIdOrApp as FirestoreDatabase;
328321
if (!external.projectId) {
@@ -332,15 +325,14 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
332325
);
333326
}
334327

335-
config.databaseId = new DatabaseId(external.projectId, external.database);
328+
this._databaseId = new DatabaseId(external.projectId, external.database);
336329
// Use a default persistenceKey that lines up with FirebaseApp.
337-
config.persistenceKey = '[DEFAULT]';
338-
config.credentials = new EmptyCredentialsProvider();
330+
this._persistenceKey = '[DEFAULT]';
331+
this._credentials = new EmptyCredentialsProvider();
339332
}
340333

341-
config.settings = new FirestoreSettings({});
342-
this._config = config;
343-
this._databaseId = config.databaseId;
334+
this._settings = new FirestoreSettings({});
335+
this._dataConverter = this.createDataConverter(this._databaseId);
344336
}
345337

346338
settings(settingsLiteral: firestore.Settings): void {
@@ -358,7 +350,7 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
358350
}
359351

360352
const newSettings = new FirestoreSettings(settingsLiteral);
361-
if (this._firestoreClient && !this._config.settings.isEqual(newSettings)) {
353+
if (this._firestoreClient && !this._settings.isEqual(newSettings)) {
362354
throw new FirestoreError(
363355
Code.FAILED_PRECONDITION,
364356
'Firestore has already been started and its settings can no longer ' +
@@ -367,11 +359,9 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
367359
);
368360
}
369361

370-
this._config.settings = newSettings;
362+
this._settings = newSettings;
371363
if (newSettings.credentials !== undefined) {
372-
this._config.credentials = makeCredentialsProvider(
373-
newSettings.credentials
374-
);
364+
this._credentials = makeCredentialsProvider(newSettings.credentials);
375365
}
376366
}
377367

@@ -416,7 +406,7 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
416406

417407
return this.configureClient(
418408
new IndexedDbPersistenceSettings(
419-
this._config.settings.cacheSizeBytes,
409+
this._settings.cacheSizeBytes,
420410
synchronizeTabs
421411
)
422412
);
@@ -489,30 +479,38 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
489479

490480
private makeDatabaseInfo(): DatabaseInfo {
491481
return new DatabaseInfo(
492-
this._config.databaseId,
493-
this._config.persistenceKey,
494-
this._config.settings.host,
495-
this._config.settings.ssl,
496-
this._config.settings.forceLongPolling
482+
this._databaseId,
483+
this._persistenceKey,
484+
this._settings.host,
485+
this._settings.ssl,
486+
this._settings.forceLongPolling
497487
);
498488
}
499489

500490
private configureClient(
501491
persistenceSettings: InternalPersistenceSettings
502492
): Promise<void> {
503-
assert(
504-
!!this._config.settings.host,
505-
'FirestoreSettings.host cannot be falsey'
506-
);
493+
assert(!!this._settings.host, 'FirestoreSettings.host is not set');
507494

508495
assert(!this._firestoreClient, 'configureClient() called multiple times');
509496

510497
const databaseInfo = this.makeDatabaseInfo();
511498

499+
this._firestoreClient = new FirestoreClient(
500+
PlatformSupport.getPlatform(),
501+
databaseInfo,
502+
this._credentials,
503+
this._queue
504+
);
505+
506+
return this._firestoreClient.start(persistenceSettings);
507+
}
508+
509+
private createDataConverter(databaseId: DatabaseId): UserDataConverter {
512510
const preConverter = (value: unknown): unknown => {
513511
if (value instanceof DocumentReference) {
514-
const thisDb = this._config.databaseId;
515-
const otherDb = value.firestore._config.databaseId;
512+
const thisDb = databaseId;
513+
const otherDb = value.firestore._databaseId;
516514
if (!otherDb.isEqual(thisDb)) {
517515
throw new FirestoreError(
518516
Code.INVALID_ARGUMENT,
@@ -521,21 +519,12 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
521519
`for database ${thisDb.projectId}/${thisDb.database}`
522520
);
523521
}
524-
return new DocumentKeyReference(this._config.databaseId, value._key);
522+
return new DocumentKeyReference(databaseId, value._key);
525523
} else {
526524
return value;
527525
}
528526
};
529-
this._dataConverter = new UserDataConverter(preConverter);
530-
531-
this._firestoreClient = new FirestoreClient(
532-
PlatformSupport.getPlatform(),
533-
databaseInfo,
534-
this._config.credentials,
535-
this._queue
536-
);
537-
538-
return this._firestoreClient.start(persistenceSettings);
527+
return new UserDataConverter(preConverter);
539528
}
540529

541530
private static databaseIdFromApp(app: FirebaseApp): DatabaseId {
@@ -558,14 +547,14 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
558547
}
559548

560549
get app(): FirebaseApp {
561-
if (!this._config.firebaseApp) {
550+
if (!this._firebaseApp) {
562551
throw new FirestoreError(
563552
Code.FAILED_PRECONDITION,
564553
"Firestore was not initialized using the Firebase SDK. 'app' is " +
565554
'not available'
566555
);
567556
}
568-
return this._config.firebaseApp;
557+
return this._firebaseApp;
569558
}
570559

571560
INTERNAL = {
@@ -668,7 +657,7 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
668657
// Note: this is not a property because the minifier can't work correctly with
669658
// the way TypeScript compiler outputs properties.
670659
_areTimestampsInSnapshotsEnabled(): boolean {
671-
return this._config.settings.timestampsInSnapshots;
660+
return this._settings.timestampsInSnapshots;
672661
}
673662
}
674663

packages/firestore/src/core/event_manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { DocumentViewChange, ChangeType, ViewSnapshot } from './view_snapshot';
2828
* tracked by EventManager.
2929
*/
3030
class QueryListenersInfo {
31-
viewSnap: ViewSnapshot | null;
32-
targetId: TargetId;
31+
viewSnap: ViewSnapshot | null = null;
32+
targetId: TargetId = 0;
3333
listeners: QueryListener[] = [];
3434
}
3535

@@ -166,7 +166,7 @@ export class QueryListener {
166166

167167
private options: ListenOptions;
168168

169-
private snap: ViewSnapshot;
169+
private snap: ViewSnapshot | null = null;
170170

171171
private onlineState: OnlineState = OnlineState.Unknown;
172172

packages/firestore/src/core/firestore_client.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,18 @@ export class FirestoreClient {
9999
// initialization completes before any other work is queued, we're cheating
100100
// with the types rather than littering the code with '!' or unnecessary
101101
// undefined checks.
102-
private eventMgr: EventManager;
103-
private persistence: Persistence;
104-
private localStore: LocalStore;
105-
private remoteStore: RemoteStore;
106-
private syncEngine: SyncEngine;
102+
private eventMgr!: EventManager;
103+
private persistence!: Persistence;
104+
private localStore!: LocalStore;
105+
private remoteStore!: RemoteStore;
106+
private syncEngine!: SyncEngine;
107+
// PORTING NOTE: SharedClientState is only used for multi-tab web.
108+
private sharedClientState!: SharedClientState;
109+
107110
private lruScheduler?: LruScheduler;
108111

109112
private readonly clientId = AutoId.newId();
110113

111-
// PORTING NOTE: SharedClientState is only used for multi-tab web.
112-
private sharedClientState: SharedClientState;
113-
114114
constructor(
115115
private platform: Platform,
116116
private databaseInfo: DatabaseInfo,

packages/firestore/src/core/sync_engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class LimboResolution {
110110
* decide whether it needs to manufacture a delete event for the target once
111111
* the target is CURRENT.
112112
*/
113-
receivedDocument: boolean;
113+
receivedDocument: boolean = false;
114114
}
115115

116116
/**

packages/firestore/src/core/target_id_generator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ enum GeneratorIds {
4242
// TODO(mrschmidt): Explore removing this class in favor of generating these IDs
4343
// directly in SyncEngine and LocalStore.
4444
export class TargetIdGenerator {
45-
private nextId: TargetId;
45+
// Initialized in the constructor via call to seek().
46+
private nextId!: TargetId;
4647

4748
/**
4849
* Instantiates a new TargetIdGenerator. If a seed is provided, the generator

packages/firestore/src/core/transaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class Transaction {
4040
* A deferred usage error that occurred previously in this transaction that
4141
* will cause the transaction to fail once it actually commits.
4242
*/
43-
private lastWriteError: FirestoreError;
43+
private lastWriteError: FirestoreError | null = null;
4444

4545
/**
4646
* Set of documents that have been written in the transaction.

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,23 +236,28 @@ export class IndexedDbPersistence implements Persistence {
236236
private readonly document: Document | null;
237237
private readonly window: Window;
238238

239-
private simpleDb: SimpleDb;
239+
// Technically these types should be `| undefined` because they are
240+
// initialized asynchronously by start(), but that would be more misleading
241+
// than useful.
242+
private simpleDb!: SimpleDb;
243+
private listenSequence!: ListenSequence;
244+
240245
private _started = false;
241246
private isPrimary = false;
242247
private networkEnabled = true;
243248
private dbName: string;
244249

245250
/** Our window.unload handler, if registered. */
246-
private windowUnloadHandler: (() => void) | null;
251+
private windowUnloadHandler: (() => void) | null = null;
247252
private inForeground = false;
248253

249254
private serializer: LocalSerializer;
250255

251256
/** Our 'visibilitychange' listener if registered. */
252-
private documentVisibilityHandler: ((e?: Event) => void) | null;
257+
private documentVisibilityHandler: ((e?: Event) => void) | null = null;
253258

254259
/** The client metadata refresh task. */
255-
private clientMetadataRefresher: CancelablePromise<void>;
260+
private clientMetadataRefresher: CancelablePromise<void> | null = null;
256261

257262
/** The last time we garbage collected the Remote Document Changelog. */
258263
private lastGarbageCollectionTime = Number.NEGATIVE_INFINITY;
@@ -267,7 +272,6 @@ export class IndexedDbPersistence implements Persistence {
267272
private readonly indexManager: IndexedDbIndexManager;
268273
private readonly remoteDocumentCache: IndexedDbRemoteDocumentCache;
269274
private readonly webStorage: Storage;
270-
private listenSequence: ListenSequence;
271275
readonly referenceDelegate: IndexedDbLruDelegate;
272276

273277
// Note that `multiClientParams` must be present to enable multi-client support while multi-tab
@@ -692,6 +696,7 @@ export class IndexedDbPersistence implements Persistence {
692696
this.markClientZombied();
693697
if (this.clientMetadataRefresher) {
694698
this.clientMetadataRefresher.cancel();
699+
this.clientMetadataRefresher = null;
695700
}
696701
this.detachVisibilityHandler();
697702
this.detachWindowUnloadHook();
@@ -1131,7 +1136,7 @@ function clientMetadataStore(
11311136

11321137
/** Provides LRU functionality for IndexedDB persistence. */
11331138
export class IndexedDbLruDelegate implements ReferenceDelegate, LruDelegate {
1134-
private inMemoryPins: ReferenceSet | null;
1139+
private inMemoryPins: ReferenceSet | null = null;
11351140

11361141
readonly garbageCollector: LruGarbageCollector;
11371142

packages/firestore/src/local/lru_garbage_collector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ const REGULAR_GC_DELAY_MS = 5 * 60 * 1000;
217217
* whether or not GC is enabled, as well as which delay to use before the next run.
218218
*/
219219
export class LruScheduler {
220-
private hasRun: boolean;
220+
private hasRun: boolean = false;
221221
private gcTask: CancelablePromise<void> | null;
222222

223223
constructor(

0 commit comments

Comments
 (0)