Skip to content

Commit b42a1b4

Browse files
authored
fix(NODE-3058): accept null or undefined anywhere we permit nullish values (#2921)
1 parent dc6e2d6 commit b42a1b4

23 files changed

+60
-47
lines changed

.eslintrc.json

+18-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"tsdoc/syntax": "warn",
2828

2929
"no-console": "error",
30-
"eqeqeq": ["error", "always", { "null": "ignore" }],
30+
"eqeqeq": ["error", "always", {"null": "ignore"}],
3131
"strict": ["error", "global"],
3232

3333
"@typescript-eslint/no-explicit-any": "off",
@@ -36,15 +36,30 @@
3636
"error",
3737
{
3838
"selector": "TSEnumDeclaration",
39-
"message": "Don't declare enums"
39+
"message": "Do not declare enums"
40+
},
41+
{
42+
"selector": "BinaryExpression[operator=/[=!]==/] Identifier[name='undefined']",
43+
"message": "Do not strictly check undefined"
44+
},
45+
{
46+
"selector": "BinaryExpression[operator=/[=!]==/] Literal[raw='null']",
47+
"message": "Do not strictly check null"
48+
},
49+
{
50+
"selector": "BinaryExpression[operator=/[=!]==?/] Literal[value='undefined']",
51+
"message": "Do not strictly check typeof undefined (NOTE: currently this rule only detects the usage of 'undefined' string literal so this could be a misfire)"
4052
}
4153
]
4254
},
4355
"overrides": [{
4456
"files": ["*.d.ts"],
4557
"rules": {
4658
"prettier/prettier": "off",
47-
"@typescript-eslint/no-empty-interface": "off"
59+
"@typescript-eslint/no-empty-interface": "off",
60+
"@typescript-eslint/no-misused-new": "off",
61+
"@typescript-eslint/ban-types": "off",
62+
"@typescript-eslint/no-unused-vars": "off"
4863
}
4964
},
5065
{

src/cmap/auth/scram.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ const hiLengthMap = {
311311
function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: CryptoMethod) {
312312
// omit the work if already generated
313313
const key = [data, salt.toString('base64'), iterations].join('_');
314-
if (_hiCache[key] !== undefined) {
314+
if (_hiCache[key] != null) {
315315
return _hiCache[key];
316316
}
317317

src/cmap/command_monitoring_events.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,15 @@ function extractCommand(command: WriteProtocolMessageType): Document {
212212
// up-convert legacy find command
213213
result = { find: collectionName(command) };
214214
Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => {
215-
if (typeof command.query[key] !== 'undefined') {
215+
if (command.query[key] != null) {
216216
result[LEGACY_FIND_QUERY_MAP[key]] = deepCopy(command.query[key]);
217217
}
218218
});
219219
}
220220

221221
Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => {
222222
const legacyKey = key as keyof typeof LEGACY_FIND_OPTIONS_MAP;
223-
if (typeof command[legacyKey] !== 'undefined') {
223+
if (command[legacyKey] != null) {
224224
result[LEGACY_FIND_OPTIONS_MAP[legacyKey]] = deepCopy(command[legacyKey]);
225225
}
226226
});
@@ -232,7 +232,7 @@ function extractCommand(command: WriteProtocolMessageType): Document {
232232
}
233233
});
234234

235-
if (typeof command.pre32Limit !== 'undefined') {
235+
if (command.pre32Limit != null) {
236236
result.limit = command.pre32Limit;
237237
}
238238

@@ -286,7 +286,7 @@ function extractReply(command: WriteProtocolMessageType, reply?: Document) {
286286
}
287287

288288
// is this a legacy find command?
289-
if (command.query && typeof command.query.$query !== 'undefined') {
289+
if (command.query && command.query.$query != null) {
290290
return {
291291
ok: 1,
292292
cursor: {

src/cmap/connection.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
343343
options: CommandOptions | undefined,
344344
callback: Callback
345345
): void {
346-
if (typeof ns.db === 'undefined' || typeof ns === 'string') {
346+
if (!(ns instanceof MongoDBNamespace)) {
347347
// TODO(NODE-3483): Replace this with a MongoCommandError
348-
throw new MongoDriverError('Namespace cannot be a string');
348+
throw new MongoDriverError('Must provide a MongoDBNamespace instance');
349349
}
350350

351351
const readPreference = getReadPreference(cmd, options);
@@ -453,7 +453,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
453453

454454
/** @internal */
455455
query(ns: MongoDBNamespace, cmd: Document, options: QueryOptions, callback: Callback): void {
456-
const isExplain = typeof cmd.$explain !== 'undefined';
456+
const isExplain = cmd.$explain != null;
457457
const readPreference = options.readPreference ?? ReadPreference.primary;
458458
const batchSize = options.batchSize || 0;
459459
const limit = options.limit;

src/cmap/stream_description.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class StreamDescription {
5151
receiveResponse(response: Document): void {
5252
this.type = parseServerType(response);
5353
RESPONSE_FIELDS.forEach(field => {
54-
if (typeof response[field] !== 'undefined') {
54+
if (typeof response[field] != null) {
5555
this[field] = response[field];
5656
}
5757

src/collection.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ export class Collection<TSchema extends Document = Document> {
699699
options?: FindOptions<TSchema> | Callback<TSchema | undefined>,
700700
callback?: Callback<TSchema>
701701
): Promise<TSchema | undefined> | void {
702-
if (callback !== undefined && typeof callback !== 'function') {
702+
if (callback != null && typeof callback !== 'function') {
703703
throw new MongoInvalidArgumentError(
704704
'Third parameter to `findOne()` must be a callback or undefined'
705705
);
@@ -1088,7 +1088,7 @@ export class Collection<TSchema extends Document = Document> {
10881088
options?: CountDocumentsOptions | Callback<number>,
10891089
callback?: Callback<number>
10901090
): Promise<number> | void {
1091-
if (typeof filter === 'undefined') {
1091+
if (filter == null) {
10921092
(filter = {}), (options = {}), (callback = undefined);
10931093
} else if (typeof filter === 'function') {
10941094
(callback = filter as Callback<number>), (filter = {}), (options = {});

src/connection_string.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ function toRecord(value: string): Record<string, any> {
177177
const keyValuePairs = value.split(',');
178178
for (const keyValue of keyValuePairs) {
179179
const [key, value] = keyValue.split(':');
180-
if (typeof value === 'undefined') {
180+
if (value == null) {
181181
throw new MongoParseError('Cannot have undefined values in key value pairs');
182182
}
183183
try {
@@ -219,7 +219,7 @@ export function parseOptions(
219219
mongoClient: MongoClient | MongoClientOptions | undefined = undefined,
220220
options: MongoClientOptions = {}
221221
): MongoOptions {
222-
if (typeof mongoClient !== 'undefined' && !(mongoClient instanceof MongoClient)) {
222+
if (mongoClient != null && !(mongoClient instanceof MongoClient)) {
223223
options = mongoClient;
224224
mongoClient = undefined;
225225
}
@@ -284,7 +284,7 @@ export function parseOptions(
284284
}
285285

286286
const objectOptions = new CaseInsensitiveMap(
287-
Object.entries(options).filter(([, v]) => (v ?? null) !== null)
287+
Object.entries(options).filter(([, v]) => v != null)
288288
);
289289

290290
const allOptions = new CaseInsensitiveMap();
@@ -418,7 +418,7 @@ function setOption(
418418
mongoOptions[name] = getUint(name, values[0]);
419419
break;
420420
case 'string':
421-
if (values[0] === undefined) {
421+
if (values[0] == null) {
422422
break;
423423
}
424424
mongoOptions[name] = String(values[0]);
@@ -1051,6 +1051,6 @@ export const OPTIONS = {
10511051

10521052
export const DEFAULT_OPTIONS = new CaseInsensitiveMap(
10531053
Object.entries(OPTIONS)
1054-
.filter(([, descriptor]) => typeof descriptor.default !== 'undefined')
1054+
.filter(([, descriptor]) => descriptor.default != null)
10551055
.map(([k, d]) => [k, d.default])
10561056
);

src/cursor/abstract_cursor.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export abstract class AbstractCursor<
150150
this[kOptions].batchSize = options.batchSize;
151151
}
152152

153-
if (typeof options.comment !== 'undefined') {
153+
if (options.comment != null) {
154154
this[kOptions].comment = options.comment;
155155
}
156156

@@ -225,9 +225,7 @@ export abstract class AbstractCursor<
225225
return {
226226
next: () =>
227227
this.next().then(value =>
228-
value !== null && value !== undefined
229-
? { value, done: false }
230-
: { value: undefined, done: true }
228+
value != null ? { value, done: false } : { value: undefined, done: true }
231229
)
232230
};
233231
}
@@ -817,7 +815,7 @@ function makeCursorStream<TSchema extends Document>(cursor: AbstractCursor<TSche
817815
function readNext() {
818816
needToClose = false;
819817
next(cursor, true, (err, result) => {
820-
needToClose = err ? !cursor.closed : result !== null;
818+
needToClose = err ? !cursor.closed : result != null;
821819

822820
if (err) {
823821
// NOTE: This is questionable, but we have a test backing the behavior. It seems the
@@ -839,7 +837,7 @@ function makeCursorStream<TSchema extends Document>(cursor: AbstractCursor<TSche
839837
return readable.destroy(err);
840838
}
841839

842-
if (result === null) {
840+
if (result == null) {
843841
readable.push(null);
844842
} else if (readable.destroyed) {
845843
cursor.close();

src/cursor/aggregation_cursor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class AggregationCursor<TSchema = Document> extends AbstractCursor<TSchem
8686
callback?: Callback<Document>
8787
): Promise<Document> | void {
8888
if (typeof verbosity === 'function') (callback = verbosity), (verbosity = true);
89-
if (verbosity === undefined) verbosity = true;
89+
if (verbosity == null) verbosity = true;
9090

9191
return executeOperation(
9292
this.topology,

src/cursor/find_cursor.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class FindCursor<TSchema = Document> extends AbstractCursor<TSchema> {
5252
this[kFilter] = filter || {};
5353
this[kBuiltOptions] = options;
5454

55-
if (typeof options.sort !== 'undefined') {
55+
if (options.sort != null) {
5656
this[kBuiltOptions].sort = formatSort(options.sort);
5757
}
5858
}
@@ -155,7 +155,7 @@ export class FindCursor<TSchema = Document> extends AbstractCursor<TSchema> {
155155
callback?: Callback<Document>
156156
): Promise<Document> | void {
157157
if (typeof verbosity === 'function') (callback = verbosity), (verbosity = true);
158-
if (verbosity === undefined) verbosity = true;
158+
if (verbosity == null) verbosity = true;
159159

160160
return executeOperation(
161161
this.topology,

src/error.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ export function isResumableError(error?: MongoError, wireVersion?: number): bool
898898
return true;
899899
}
900900

901-
if (typeof wireVersion !== 'undefined' && wireVersion >= 9) {
901+
if (wireVersion != null && wireVersion >= 9) {
902902
// DRIVERS-1308: For 4.4 drivers running against 4.4 servers, drivers will add a special case to treat the CursorNotFound error code as resumable
903903
if (error && error instanceof MongoError && error.code === 43) {
904904
return true;

src/explain.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class Explain {
4040
}
4141

4242
static fromOptions(options?: ExplainOptions): Explain | undefined {
43-
if (options?.explain === undefined) return;
43+
if (options?.explain == null) return;
4444

4545
const explain = options.explain;
4646
if (typeof explain === 'boolean' || typeof explain === 'string') {

src/operations/command.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ export abstract class CommandOperation<T> extends AbstractOperation<T> {
9797

9898
if (this.hasAspect(Aspect.EXPLAINABLE)) {
9999
this.explain = Explain.fromOptions(options);
100-
} else if (options?.explain !== undefined) {
100+
} else if (options?.explain != null) {
101101
throw new MongoInvalidArgumentError(`Option "explain" is not supported on this command`);
102102
}
103103
}
104104

105105
get canRetryWrite(): boolean {
106106
if (this.hasAspect(Aspect.EXPLAINABLE)) {
107-
return this.explain === undefined;
107+
return this.explain == null;
108108
}
109109
return true;
110110
}

src/operations/count.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class CountOperation extends CommandOperation<number> {
4747
cmd.skip = options.skip;
4848
}
4949

50-
if (typeof options.hint !== 'undefined') {
50+
if (options.hint != null) {
5151
cmd.hint = options.hint;
5252
}
5353

src/operations/delete.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class DeleteOperation extends CommandOperation<Document> {
6464
return false;
6565
}
6666

67-
return this.statements.every(op => (typeof op.limit !== 'undefined' ? op.limit > 0 : true));
67+
return this.statements.every(op => (op.limit != null ? op.limit > 0 : true));
6868
}
6969

7070
execute(server: Server, session: ClientSession, callback: Callback): void {
@@ -80,7 +80,7 @@ export class DeleteOperation extends CommandOperation<Document> {
8080
command.let = options.let;
8181
}
8282

83-
if (options.explain !== undefined && maxWireVersion(server) < 3) {
83+
if (options.explain != null && maxWireVersion(server) < 3) {
8484
return callback
8585
? callback(
8686
new MongoCompatibilityError(`Server ${server.name} does not support explain on delete`)

src/operations/find.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class FindOperation extends CommandOperation<Document> {
106106

107107
const serverWireVersion = maxWireVersion(server);
108108
const options = this.options;
109-
if (typeof options.allowDiskUse !== 'undefined' && serverWireVersion < 4) {
109+
if (options.allowDiskUse != null && serverWireVersion < 4) {
110110
callback(
111111
new MongoCompatibilityError('Option "allowDiskUse" is not supported on MongoDB < 3.2')
112112
);
@@ -338,7 +338,7 @@ function makeLegacyFindCommand(
338338
findCommand.$maxTimeMS = options.maxTimeMS;
339339
}
340340

341-
if (typeof options.explain !== 'undefined') {
341+
if (options.explain != null) {
342342
findCommand.$explain = true;
343343
}
344344

src/operations/insert.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class InsertOperation extends CommandOperation<Document> {
3737
command.bypassDocumentValidation = options.bypassDocumentValidation;
3838
}
3939

40-
if (typeof options.comment !== 'undefined') {
40+
if (options.comment != null) {
4141
command.comment = options.comment;
4242
}
4343

src/operations/update.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class UpdateOneOperation extends UpdateOperation {
159159
): void {
160160
super.execute(server, session, (err, res) => {
161161
if (err || !res) return callback(err);
162-
if (typeof this.explain !== 'undefined') return callback(undefined, res);
162+
if (this.explain != null) return callback(undefined, res);
163163
if (res.code) return callback(new MongoServerError(res));
164164
if (res.writeErrors) return callback(new MongoServerError(res.writeErrors[0]));
165165

@@ -196,7 +196,7 @@ export class UpdateManyOperation extends UpdateOperation {
196196
): void {
197197
super.execute(server, session, (err, res) => {
198198
if (err || !res) return callback(err);
199-
if (typeof this.explain !== 'undefined') return callback(undefined, res);
199+
if (this.explain != null) return callback(undefined, res);
200200
if (res.code) return callback(new MongoServerError(res));
201201
if (res.writeErrors) return callback(new MongoServerError(res.writeErrors[0]));
202202

@@ -250,7 +250,7 @@ export class ReplaceOneOperation extends UpdateOperation {
250250
): void {
251251
super.execute(server, session, (err, res) => {
252252
if (err || !res) return callback(err);
253-
if (typeof this.explain !== 'undefined') return callback(undefined, res);
253+
if (this.explain != null) return callback(undefined, res);
254254
if (res.code) return callback(new MongoServerError(res));
255255
if (res.writeErrors) return callback(new MongoServerError(res.writeErrors[0]));
256256

src/read_preference.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class ReadPreference {
8686
if (!ReadPreference.isValid(mode)) {
8787
throw new MongoInvalidArgumentError(`Invalid read preference mode ${JSON.stringify(mode)}`);
8888
}
89-
if (options === undefined && typeof tags === 'object' && !Array.isArray(tags)) {
89+
if (options == null && typeof tags === 'object' && !Array.isArray(tags)) {
9090
options = tags;
9191
tags = undefined;
9292
} else if (tags && !Array.isArray(tags)) {

src/sdam/topology_description.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class TopologyDescription {
113113
break;
114114
}
115115

116-
if (this.logicalSessionTimeoutMinutes === undefined) {
116+
if (this.logicalSessionTimeoutMinutes == null) {
117117
// First server with a non null logicalSessionsTimeout
118118
this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
119119
continue;

src/sessions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ export function applySession(
854854
Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
855855
} else if (session[kSnapshotEnabled]) {
856856
command.readConcern = command.readConcern || { level: ReadConcernLevel.snapshot };
857-
if (session[kSnapshotTime] !== undefined) {
857+
if (session[kSnapshotTime] != null) {
858858
Object.assign(command.readConcern, { atClusterTime: session[kSnapshotTime] });
859859
}
860860
}
@@ -897,7 +897,7 @@ export function updateSessionFromResponse(session: ClientSession, document: Docu
897897
session.transaction._recoveryToken = document.recoveryToken;
898898
}
899899

900-
if (session?.[kSnapshotEnabled] && session[kSnapshotTime] === undefined) {
900+
if (session?.[kSnapshotEnabled] && session[kSnapshotTime] == null) {
901901
// find and aggregate commands return atClusterTime on the cursor
902902
// distinct includes it in the response body
903903
const atClusterTime = document.cursor?.atClusterTime || document.atClusterTime;

0 commit comments

Comments
 (0)