diff --git a/BUILD.bazel b/BUILD.bazel index 6f033e52855..ac9e8d4b243 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,5 @@ load("@npm//:defs.bzl", "npm_link_all_packages") +load("@aspect_rules_js//npm:defs.bzl", "npm_link_package") load("//bazel/install_rules:install_rules.bzl", "mongo_install") load("//bazel/toolchains:mongo_toolchain.bzl", "setup_mongo_toolchain_aliases") load("//bazel/config:render_template.bzl", "render_template") @@ -19,13 +20,19 @@ exports_files([ npm_link_all_packages(name = "node_modules") +npm_link_package( + name = "node_modules/eslint-plugin-mongodb", + src = "//buildscripts/eslint-plugin-mongodb:npm_package", +) + js_library( name = "eslintrc", srcs = ["eslint.config.mjs"], deps = [ + ":node_modules/@eslint/eslintrc", ":node_modules/@eslint/js", + ":node_modules/eslint-plugin-mongodb", ":node_modules/globals", - "//:node_modules/@eslint/eslintrc", ], ) diff --git a/buildscripts/eslint-plugin-mongodb/BUILD.bazel b/buildscripts/eslint-plugin-mongodb/BUILD.bazel new file mode 100644 index 00000000000..81578461fa9 --- /dev/null +++ b/buildscripts/eslint-plugin-mongodb/BUILD.bazel @@ -0,0 +1,13 @@ +load("@aspect_rules_js//npm:defs.bzl", "npm_package") + +npm_package( + name = "npm_package", + srcs = [ + "package.json", + "plugin.js", + "rules/no-print-fn.js", + "rules/no-tojson-fn.js", + ], + package = "eslint-plugin-mongodb", + visibility = ["//visibility:public"], +) diff --git a/buildscripts/eslint-plugin-mongodb/package.json b/buildscripts/eslint-plugin-mongodb/package.json new file mode 100644 index 00000000000..6866699dd0e --- /dev/null +++ b/buildscripts/eslint-plugin-mongodb/package.json @@ -0,0 +1,7 @@ +{ + "name": "eslint-plugin-mongodb", + "version": "1.0.0", + "private": true, + "main": "plugin.js", + "type": "module" +} diff --git a/buildscripts/eslint-plugin-mongodb/plugin.js b/buildscripts/eslint-plugin-mongodb/plugin.js new file mode 100644 index 00000000000..f1d41b1a4ab --- /dev/null +++ b/buildscripts/eslint-plugin-mongodb/plugin.js @@ -0,0 +1,11 @@ +// plugin.js + +import {default as no_print} from "./rules/no-print-fn.js"; +import {default as no_tojson} from "./rules/no-tojson-fn.js"; + +export default { + rules: { + "no-print-fn": no_print, + "no-tojson-fn": no_tojson, + }, +}; diff --git a/buildscripts/eslint-plugin-mongodb/rules/no-print-fn.js b/buildscripts/eslint-plugin-mongodb/rules/no-print-fn.js new file mode 100644 index 00000000000..3426fe5f1db --- /dev/null +++ b/buildscripts/eslint-plugin-mongodb/rules/no-print-fn.js @@ -0,0 +1,35 @@ +const stopList = [ + "print", + "printjson", + "printjsononeline", +]; + +export default { + + meta: { + type: "problem", + docs: { + description: "Ensure no direct calls to print* functions", + }, + fixable: "code", + }, + + create(context) { + return { + CallExpression: function(node) { + if (node.callee.type == "Identifier" && + stopList.some(fn => fn == node.callee.name)) { + context.report( + { + node, + message: `Direct use of '${ + node.callee + .name}()'. Consider using jsTest.log.info() instead or disable mongodb/no-print-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn' + +More about rules configuration: https://eslint.org/docs/latest/use/configure/rules`, + }); + } + } + }; + } +}; diff --git a/buildscripts/eslint-plugin-mongodb/rules/no-tojson-fn.js b/buildscripts/eslint-plugin-mongodb/rules/no-tojson-fn.js new file mode 100644 index 00000000000..e3b86612aae --- /dev/null +++ b/buildscripts/eslint-plugin-mongodb/rules/no-tojson-fn.js @@ -0,0 +1,34 @@ +const stopList = [ + "tojson", + "tojsononeline", +]; + +export default { + + meta: { + type: "problem", + docs: { + description: "Ensure no direct calls to tojson* functions", + }, + fixable: "code", + }, + + create(context) { + return { + CallExpression: function(node) { + if (node.callee.type == "Identifier" && + stopList.some(fn => fn == node.callee.name)) { + context.report( + { + node, + message: `Direct use of '${ + node.callee + .name}()'. Consider using jsTest.log.info() instead or disable mongodb/no-tojson-fn rule when necessary, e.g., '// eslint-disable-next-line mongodb/no-print-fn' + +More about rules configuration: https://eslint.org/docs/latest/use/configure/rules`, + }); + } + } + }; + } +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index 3553ca81f5b..99d58a90e0d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,10 @@ -import globals from "globals"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; +import {FlatCompat} from "@eslint/eslintrc"; import eslint from "@eslint/js"; import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import {default as mongodb_plugin} from "eslint-plugin-mongodb"; +import globals from "globals"; +import path from "node:path"; +import {fileURLToPath} from "node:url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -13,253 +14,306 @@ const compat = new FlatCompat({ allConfig: js.configs.all }); -export default [{ - ignores: ["src/mongo/gotools/*", "**/*.tpl.js", "jstests/third_party/**/*.js"], -}, ...compat.extends("eslint:recommended"), { - languageOptions: { - globals: { - ...globals.mongo, - TestData: true, - WriteError: true, - WriteCommandError: true, - BulkWriteError: true, - DB: true, - DBCollection: true, - DBQuery: true, - DBExplainQuery: true, - DBCommandCursor: true, - MongoBridge: true, - MongoURI: true, - WriteConcern: true, - SessionOptions: true, - CollInfos: true, - CountDownLatch: true, - BSONAwareMap: true, - latestFCV: true, - lastLTSFCV: true, - lastContinuousFCV: true, - checkFCV: true, - isFCVEqual: true, - binVersionToFCV: true, - numVersionsSinceLastLTS: true, - getFCVConstants: true, - removeFCVDocument: true, - targetFCV: true, - AssertionError: true, - assert: true, - doassert: true, - authutil: true, - tojson: true, - tojsononeline: true, - tostrictjson: true, - tojsonObject: true, - toEJSON: true, - print: true, - printjson: true, - printjsononeline: true, - jsTest: true, - jsTestLog: true, - jsonTestLog: true, - ErrorCodes: true, - ErrorCodeStrings: true, - checkProgram: true, - Random: true, - checkLog: true, - sleep: true, - resetDbpath: true, - copyDbpath: true, - jsTestName: true, - startParallelShell: true, - buildInfo: true, - getBuildInfo: true, - jsTestOptions: true, - printShardingStatus: true, - _getErrorWithCode: true, - isNetworkError: true, - __magicNoPrint: true, - computeSHA256Block: true, - emit: true, - _awaitRSHostViaRSMonitor: true, - convertShardKeyToHashed: true, - benchRun: true, - benchRunSync: true, - gc: true, - DataConsistencyChecker: true, - isNumber: true, - isObject: true, - isString: true, - _createSecurityToken: true, - _createTenantToken: true, - _isAddressSanitizerActive: true, - _isLeakSanitizerActive: true, - _isThreadSanitizerActive: true, - _isUndefinedBehaviorSanitizerActive: true, - _isSpiderMonkeyDebugEnabled: true, - _optimizationsEnabled: true, - allocatePort: true, - allocatePorts: true, - resetAllocatedPorts: true, - bsonObjToArray: true, - _writeTestPipeObjects: true, - _writeTestPipe: true, - _writeTestPipeBsonFile: true, - _readTestPipes: true, - runFeatureFlagMultiversionTest: true, - isRetryableError: true, - numberDecimalsAlmostEqual: true, - numberDecimalsEqual: true, - debug: true, - bsonsize: true, - _DelegatingDriverSession: true, - _DummyDriverSession: true, - _ServerSession: true, - sortDoc: true, - executeNoThrowNetworkError: true, - _readDumpFile: true, - _openGoldenData: true, - _writeGoldenData: true, - _threadInject: true, - port: true, - _buildBsonObj: true, - convertTrafficRecordingToBSON: true, - _setShellFailPoint: true, - shellHelper: true, - _srand: true, - _shouldUseImplicitSessions: true, - testingReplication: true, - myPort: true, - retryOnNetworkError: true, - getJSHeapLimitMB: true, - _getEnv: true, - indentStr: true, - _forgetReplSet: true, - _fnvHashToHexString: true, - _resultSetsEqualUnordered: true, - getStringWidth: true, - _compareStringsWithCollation: true, - eventResumeTokenType: true, - highWaterMarkResumeTokenType: true, +export default [ + ...compat + .extends("eslint:recommended"), + { + ignores: ["src/mongo/gotools/*", "**/*.tpl.js", "jstests/third_party/**/*.js"], + }, + { + languageOptions: { + globals: { + ...globals.mongo, + TestData: true, + WriteError: true, + WriteCommandError: true, + BulkWriteError: true, + DB: true, + DBCollection: true, + DBQuery: true, + DBExplainQuery: true, + DBCommandCursor: true, + MongoBridge: true, + MongoURI: true, + WriteConcern: true, + SessionOptions: true, + CollInfos: true, + CountDownLatch: true, + BSONAwareMap: true, + latestFCV: true, + lastLTSFCV: true, + lastContinuousFCV: true, + checkFCV: true, + isFCVEqual: true, + binVersionToFCV: true, + numVersionsSinceLastLTS: true, + getFCVConstants: true, + removeFCVDocument: true, + targetFCV: true, + AssertionError: true, + assert: true, + doassert: true, + authutil: true, + tojson: true, + tojsononeline: true, + tostrictjson: true, + tojsonObject: true, + toEJSON: true, + print: true, + printjson: true, + printjsononeline: true, + jsTest: true, + jsTestLog: true, + jsonTestLog: true, + ErrorCodes: true, + ErrorCodeStrings: true, + checkProgram: true, + Random: true, + checkLog: true, + sleep: true, + resetDbpath: true, + copyDbpath: true, + jsTestName: true, + startParallelShell: true, + buildInfo: true, + getBuildInfo: true, + jsTestOptions: true, + printShardingStatus: true, + _getErrorWithCode: true, + isNetworkError: true, + __magicNoPrint: true, + computeSHA256Block: true, + emit: true, + _awaitRSHostViaRSMonitor: true, + convertShardKeyToHashed: true, + benchRun: true, + benchRunSync: true, + gc: true, + DataConsistencyChecker: true, + isNumber: true, + isObject: true, + isString: true, + _createSecurityToken: true, + _createTenantToken: true, + _isAddressSanitizerActive: true, + _isLeakSanitizerActive: true, + _isThreadSanitizerActive: true, + _isUndefinedBehaviorSanitizerActive: true, + _isSpiderMonkeyDebugEnabled: true, + _optimizationsEnabled: true, + allocatePort: true, + allocatePorts: true, + resetAllocatedPorts: true, + bsonObjToArray: true, + _writeTestPipeObjects: true, + _writeTestPipe: true, + _writeTestPipeBsonFile: true, + _readTestPipes: true, + runFeatureFlagMultiversionTest: true, + isRetryableError: true, + numberDecimalsAlmostEqual: true, + numberDecimalsEqual: true, + debug: true, + bsonsize: true, + _DelegatingDriverSession: true, + _DummyDriverSession: true, + _ServerSession: true, + sortDoc: true, + executeNoThrowNetworkError: true, + _readDumpFile: true, + _openGoldenData: true, + _writeGoldenData: true, + _threadInject: true, + port: true, + _buildBsonObj: true, + convertTrafficRecordingToBSON: true, + _setShellFailPoint: true, + shellHelper: true, + _srand: true, + _shouldUseImplicitSessions: true, + testingReplication: true, + myPort: true, + retryOnNetworkError: true, + getJSHeapLimitMB: true, + _getEnv: true, + indentStr: true, + _forgetReplSet: true, + _fnvHashToHexString: true, + _resultSetsEqualUnordered: true, + getStringWidth: true, + _compareStringsWithCollation: true, + eventResumeTokenType: true, + highWaterMarkResumeTokenType: true, - // likely could be replaced with `path` - _copyFileRange: true, - appendFile: true, - copyFile: true, - writeFile: true, - fileExists: true, - pathExists: true, - umask: true, - getFileMode: true, - copyDir: true, + // likely could be replaced with `path` + _copyFileRange: true, + appendFile: true, + copyFile: true, + writeFile: true, + fileExists: true, + pathExists: true, + umask: true, + getFileMode: true, + copyDir: true, - // likely could be replaced with `child_process` - MongoRunner: true, - run: true, - runProgram: true, - runMongoProgram: true, - runNonMongoProgram: true, - runNonMongoProgramQuietly: true, - _runMongoProgram: true, - _startMongoProgram: true, - startMongoProgram: true, - _stopMongoProgram: true, - stopMongoProgramByPid: true, - clearRawMongoProgramOutput: true, - rawMongoProgramOutput: true, - waitProgram: true, - waitMongoProgram: true, - _runningMongoChildProcessIds: true, - startMongoProgramNoConnect: true, + // likely could be replaced with `child_process` + MongoRunner: true, + run: true, + runProgram: true, + runMongoProgram: true, + runNonMongoProgram: true, + runNonMongoProgramQuietly: true, + _runMongoProgram: true, + _startMongoProgram: true, + startMongoProgram: true, + _stopMongoProgram: true, + stopMongoProgramByPid: true, + clearRawMongoProgramOutput: true, + rawMongoProgramOutput: true, + waitProgram: true, + waitMongoProgram: true, + _runningMongoChildProcessIds: true, + startMongoProgramNoConnect: true, - // shell-specific - shellPrintHelper: true, - shellAutocomplete: true, - __autocomplete__: true, - defaultPrompt: true, - ___it___: true, - __promptWrapper__: true, - passwordPrompt: true, - isInteractive: true, + // shell-specific + shellPrintHelper: true, + shellAutocomplete: true, + __autocomplete__: true, + defaultPrompt: true, + ___it___: true, + __promptWrapper__: true, + passwordPrompt: true, + isInteractive: true, - // built-in BSON types and helpers - Code: true, - MaxKey: true, - MinKey: true, - HexData: true, - DBPointer: true, - DBRef: true, - BinData: true, - NumberLong: true, - NumberDecimal: true, - Timestamp: true, - MD5: true, - Geo: true, - decodeResumeToken: true, - bsonWoCompare: true, - bsonUnorderedFieldsCompare: true, - bsonBinaryEqual: true, - friendlyEqual: true, - timestampCmp: true, - decompressBSONColumn: true, + // built-in BSON types and helpers + Code: true, + MaxKey: true, + MinKey: true, + HexData: true, + DBPointer: true, + DBRef: true, + BinData: true, + NumberLong: true, + NumberDecimal: true, + Timestamp: true, + MD5: true, + Geo: true, + decodeResumeToken: true, + bsonWoCompare: true, + bsonUnorderedFieldsCompare: true, + bsonBinaryEqual: true, + friendlyEqual: true, + timestampCmp: true, + decompressBSONColumn: true, - hex_md5: true, - QueryHelpers: true, - chatty: true, - DriverSession: true, - ToolTest: true, - uncheckedParallelShellPidsString: true, - _shouldRetryWrites: true, + hex_md5: true, + QueryHelpers: true, + chatty: true, + DriverSession: true, + ToolTest: true, + uncheckedParallelShellPidsString: true, + _shouldRetryWrites: true, - // from_cpp: - __prompt__: true, - _replMonitorStats: true, + // from_cpp: + __prompt__: true, + _replMonitorStats: true, - // explainable.js - Explainable: true, + // explainable.js + Explainable: true, - // utils.js - _verboseShell: true, - __quiet: true, - printStackTrace: true, - setVerboseShell: true, - _barFormat: true, - compare: true, - compareOn: true, - shellPrint: true, - _originalPrint: true, - disablePrint: true, - enablePrint: true, - replSetMemberStatePrompt: true, - hasErrorCode: true, - helloStatePrompt: true, - _validateMemberIndex: true, - help: true, - retryOnRetryableError: true, - }, + // utils.js + _verboseShell: true, + __quiet: true, + printStackTrace: true, + setVerboseShell: true, + _barFormat: true, + compare: true, + compareOn: true, + shellPrint: true, + _originalPrint: true, + disablePrint: true, + enablePrint: true, + replSetMemberStatePrompt: true, + hasErrorCode: true, + helloStatePrompt: true, + _validateMemberIndex: true, + help: true, + retryOnRetryableError: true, + }, - ecmaVersion: 2022, - sourceType: "module", - }, + ecmaVersion: 2022, + sourceType: "module", + }, - rules: { - "no-prototype-builtins": 0, - "no-useless-escape": 0, - "no-irregular-whitespace": 0, - "no-inner-declarations": 0, + plugins: { + "mongodb": mongodb_plugin, + }, - "no-unused-vars": [0, { - varsIgnorePattern: "^_", - args: "none", - }], + rules: { + // TODO SERVER-99571 : enable mongodb/* rules. + "mongodb/no-print-fn": 1, + "mongodb/no-tojson-fn": 1, - "no-empty": 0, - "no-redeclare": 0, - "no-constant-condition": 0, - "no-loss-of-precision": 0, - semi: 2, + "no-prototype-builtins": 0, + "no-useless-escape": 0, + "no-irregular-whitespace": 0, + "no-inner-declarations": 0, - "no-restricted-syntax": ["error", { - message: "Invalid load call. Please convert your library to a module and import it instead.", - selector: "CallExpression > Identifier[name=\"load\"]", - }], - }, -}]; \ No newline at end of file + "no-unused-vars": [ + 0, + { + varsIgnorePattern: "^_", + args: "none", + } + ], + + "no-empty": 0, + "no-redeclare": 0, + "no-constant-condition": 0, + "no-loss-of-precision": 0, + semi: 2, + + "no-restricted-syntax": [ + "error", + { + message: + "Invalid load call. Please convert your library to a module and import it instead.", + selector: "CallExpression > Identifier[name=\"load\"]", + } + ], + }, + }, + { + // It's ok for golden tests to use print() and tojson() directly. + plugins: { + "mongodb": mongodb_plugin, + }, + files: [ + "jstests/libs/begin_golden_test.js", + "jstests/libs/golden_test.js", + "jstests/libs/override_methods/golden_overrides.js", + "jstests/libs/override_methods/sharded_golden_overrides.js", + "jstests/libs/query/golden_test_utils.js", + "jstests/libs/query_golden_sharding_utils.js", + "jstests/query_golden/**/*.js", + "jstests/query_golden_sharding/**/*.js", + ], + rules: { + "mongodb/no-print-fn": 0, + "mongodb/no-tojson-fn": 0, + } + }, + { + // Don't run mongodb linter rules on src/ + plugins: { + "mongodb": mongodb_plugin, + }, + files: [ + "src/**/*.js", + ], + rules: { + "mongodb/no-print-fn": 0, + "mongodb/no-tojson-fn": 0, + } + } +]; diff --git a/package.json b/package.json index b5d89ca8927..4c398163f5a 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "private": true, "devDependencies": { - "@eslint/js": "^9", "@eslint/eslintrc": "3.2.0", + "@eslint/js": "^9", "eslint": "9.19.0", - "globals": "14.0.0", "eslint-formatter-unix": "^8.40.0", + "globals": "14.0.0", "prettier": "3.4.2" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd3b38ab490..9b22c0cd9ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,28 +6,25 @@ settings: onlyBuiltDependencies: [] -importers: - - .: - devDependencies: - '@eslint/eslintrc': - specifier: 3.2.0 - version: 3.2.0 - '@eslint/js': - specifier: ^9 - version: 9.19.0 - eslint: - specifier: 9.19.0 - version: 9.19.0 - eslint-formatter-unix: - specifier: ^8.40.0 - version: 8.40.0 - globals: - specifier: 14.0.0 - version: 14.0.0 - prettier: - specifier: 3.4.2 - version: 3.4.2 +devDependencies: + '@eslint/eslintrc': + specifier: 3.2.0 + version: 3.2.0 + '@eslint/js': + specifier: ^9 + version: 9.19.0 + eslint: + specifier: 9.19.0 + version: 9.19.0 + eslint-formatter-unix: + specifier: ^8.40.0 + version: 8.40.0 + globals: + specifier: 14.0.0 + version: 14.0.0 + prettier: + specifier: 3.4.2 + version: 3.4.2 packages: