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

Lines changed: 18 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 5 additions & 5 deletions
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

Lines changed: 4 additions & 6 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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,

0 commit comments

Comments
 (0)