diff --git a/bun.lock b/bun.lock index e27057018f0..b2ee070407a 100644 --- a/bun.lock +++ b/bun.lock @@ -11,6 +11,7 @@ "bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8", "esbuild": "^0.21.5", "mitata": "^0.1.14", + "oxlint": "1.70.0", "peechy": "0.4.34", "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.3.0", @@ -144,6 +145,44 @@ "@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.70.0", "", { "os": "android", "cpu": "arm" }, "sha512-zFh0P4cswmRvw6nkyb89dr18rRanuaCPAsEXsFDoQY8WdaquI8Pt4NWFjaMJg6L23cy5NeN8J9cBnREbWzZhaw=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.70.0", "", { "os": "android", "cpu": "arm64" }, "sha512-qI8o4HZjeGiBrWv+pJv4lH0Yi2Gl/JSp/EumBUApezJprIKa5PS4nU0lQsQngtky8k+SplQIOjv6hwu0SSxeyg=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.70.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8KjgVVHI5F9nVwHCRwwA78Ty7zNKP4Wd9OeN5PSv3iu/F/u1RVXoOCgLhWqust6HmwQG6xc8c+RCyaWENy24+w=="], + + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.70.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-WVydssv5PSUBXFJTdNBWlmGkbNmvPGaFt/2SUT/EZRB6bq6bEOHmMlbnupZD5jmlEvi9+mZJHi8TCw15lyfSfQ=="], + + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.70.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hJucmUf8OlinHNb1R7fI4Fw6WsAstOz7i8nmkWQfiHoZXtbufNm+MxiDTIMk1ggh2Ro4vLzgQ+bKvRY54MZoRA=="], + + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1BnS7wbCYDSXwWzJJ+mc3NURoha6m6m6RT5c6vgAY3oz7C3OVXP+S0awo2mRq97arrJkVvO3qRQfyAHL+76xtQ=="], + + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.70.0", "", { "os": "linux", "cpu": "arm" }, "sha512-yKy/UdbR55+M2yEcuiV5DCNC/gdQAjr/GioUy50QwBzSrKm8ueWADqyRLS9Xk+qjNeCYGg6A8FvUBds56ttfqg=="], + + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0A5XJ4alvmqFUFP/4oYSyaO+qLto/HrKEWTSaegiVl+HOufFngK2BjYw9x4RbwBt/du5QG6l5q1zeWiJYYG5yg=="], + + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.70.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-JiylyurlB0CLSedNtx1gzv3FvfWPF1h/2Y3BJszPLNt5XQFlBsH5ke0Jle3iJb3uqu5m2e7A/DwzpuCAHdiU+A=="], + + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.70.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-J8VPG7I3/HmgaU4u8pNU2kFx2+0U+vPLS1dXFxXOaR/2TQ0f8AC7DRz0SRGRI1bfphnX2hVYTTtLuhL4nYKL+Q=="], + + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-N2+4lV2KLN+oXTIIIwmWDhwkrnvqf5oX7Hw0zPjk+RuIVgiBQSOlJWF7uQoFx2siEYX0ZQ5cfSbEAHm+J3t7Wg=="], + + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.70.0", "", { "os": "linux", "cpu": "none" }, "sha512-1e2L7cFCvx9QDzq6NPP+0tABKb5z6nWHyddWTNKprEsjO9xNrAtPowuCGpjNXxkTdsMiZ4jc8YQ5SstZd4XK6g=="], + + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.70.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Kwu/l/8GcYibCWA9m9N5pRXMIKVSsL/YbgpLzYkqDhWTiqdRfnNJ/+nqIKRKQiFbHWsdlHEhzMwruJK+qcEruA=="], + + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-tap04CsHYOl0nSAQJfPNIuBxqEPB2HnhQqwaOXLg1jnp2XfRo8Fa814dA4QC4zpvTWXCjAAaCY1W5LOORkEQuQ=="], + + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.70.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hzJa/WgvtJpbBD9rgfy0qe+MjbxOXNUT0bfR1S6EQQzfTtBFA9xg5q8KSwRrQ2QfSS+TaP4j+4mVPQrfNc6UNg=="], + + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.70.0", "", { "os": "none", "cpu": "arm64" }, "sha512-xbsaNSNzVSnaJACCUYr1HQMyY/Q/Q1LkePmHG3UvZPvGCYGNxrsZp9OmtA6ick8xH47ltRRbRrPCM1YXYcyC+A=="], + + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.70.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-icAEsUI7JbW1TMRdEXV83mVAInhRVQYuuAlPpxdGwJ95chNdnCzjloRW8GglT0WvzOEZSio6fnYSk2DJ2Hv7LQ=="], + + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.70.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-FHMSWbVsPVs/f+Jcl04ws4JJ2wUnauyTzlpxWRG/lSO/8GpX08Fo2gQZqdA6CrRFI+zvkxl+N/KwJGWfUwYVZA=="], + + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.70.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ptOlKwCz7n4AKs5VweMqG6DAg677FmKOK+vBkkL9DMNgFATIQ+upqUYBTOEwRQyRAx1ncGlPlXleV2hIcm3z4g=="], + "@sentry/types": ["@sentry/types@7.120.4", "", {}, "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q=="], "@types/aws-lambda": ["@types/aws-lambda@8.10.159", "", {}, "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg=="], @@ -268,6 +307,8 @@ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "oxlint": ["oxlint@1.70.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.70.0", "@oxlint/binding-android-arm64": "1.70.0", "@oxlint/binding-darwin-arm64": "1.70.0", "@oxlint/binding-darwin-x64": "1.70.0", "@oxlint/binding-freebsd-x64": "1.70.0", "@oxlint/binding-linux-arm-gnueabihf": "1.70.0", "@oxlint/binding-linux-arm-musleabihf": "1.70.0", "@oxlint/binding-linux-arm64-gnu": "1.70.0", "@oxlint/binding-linux-arm64-musl": "1.70.0", "@oxlint/binding-linux-ppc64-gnu": "1.70.0", "@oxlint/binding-linux-riscv64-gnu": "1.70.0", "@oxlint/binding-linux-riscv64-musl": "1.70.0", "@oxlint/binding-linux-s390x-gnu": "1.70.0", "@oxlint/binding-linux-x64-gnu": "1.70.0", "@oxlint/binding-linux-x64-musl": "1.70.0", "@oxlint/binding-openharmony-arm64": "1.70.0", "@oxlint/binding-win32-arm64-msvc": "1.70.0", "@oxlint/binding-win32-ia32-msvc": "1.70.0", "@oxlint/binding-win32-x64-msvc": "1.70.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-D6JgHtzkhRwvEC+A0Nw5AEc5bk8x5i1pHzvZIEf/a0C4hOzmAACNGtkDGPyFaxxX3ZVGxCPeig3P3rMM8XU3/g=="], + "param-case": ["param-case@2.1.1", "", { "dependencies": { "no-case": "^2.2.0" } }, "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w=="], "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], diff --git a/oxlint.json b/oxlint.json index debc0f141d7..5935bd0054b 100644 --- a/oxlint.json +++ b/oxlint.json @@ -1,5 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/refs/heads/main/npm/oxlint/configuration_schema.json", + "jsPlugins": ["./scripts/oxlint-plugins/bun.js"], "categories": { "correctness": "error" }, @@ -82,6 +83,15 @@ "rules": { "no-unused-expressions": "off" } + }, + { + "files": ["src/js/**"], + "rules": { + // Reading `obj.prop` in an `if` condition and again in the body hits + // the property (and any getter/Proxy trap) twice. Destructure or + // cache the value in a local instead. + "bun/no-duplicate-conditional-property-access": "error" + } } ] } diff --git a/package.json b/package.json index fda02bd39d8..abd28164496 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8", "esbuild": "^0.21.5", "mitata": "^0.1.14", + "oxlint": "1.70.0", "peechy": "0.4.34", "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.3.0", @@ -63,7 +64,7 @@ "fmt": "bun run prettier", "fmt:cpp": "bun run clang-format", "fmt:rust": "cargo fmt --all", - "lint": "bunx oxlint --config=oxlint.json --format=github src/js", + "lint": "oxlint --config=oxlint.json --format=github src/js", "lint:fix": "oxlint --config oxlint.json --fix", "test": "node scripts/runner.node.mjs --exec-path ./build/debug/bun-debug", "testleak": "BUN_DESTRUCT_VM_ON_EXIT=1 ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=malloc_context_size=100:print_suppressions=1:suppressions=$npm_config_local_prefix/test/leaksan.supp ./build/debug/bun-debug", diff --git a/scripts/oxlint-plugins/bun.js b/scripts/oxlint-plugins/bun.js new file mode 100644 index 00000000000..1793d1e9df2 --- /dev/null +++ b/scripts/oxlint-plugins/bun.js @@ -0,0 +1,221 @@ +// Custom oxlint rules for Bun's built-in JavaScript (src/js/**). +// +// Registered via `jsPlugins` in oxlint.json. Rules are written against +// oxlint's ESTree-compatible AST (see the `oxlint/plugins-dev` type +// definitions). Run with `bun run lint`. + +/** + * Return a textual key for a simple static member expression chain made of + * identifiers and `this`, e.g. `options.foo` or `this.a.b`. Returns `null` + * for anything else (computed access, calls, optional chaining, literals). + */ +function memberExpressionKey(node) { + if (!node || node.type !== "MemberExpression" || node.computed || node.optional) { + return null; + } + const { object, property } = node; + if (!property || property.type !== "Identifier") { + return null; + } + let base; + if (object.type === "Identifier") { + base = object.name; + } else if (object.type === "ThisExpression") { + base = "this"; + } else if (object.type === "MemberExpression") { + base = memberExpressionKey(object); + if (base === null) return null; + } else { + return null; + } + return base + "." + property.name; +} + +/** + * True if `node` is the target of an assignment (simple or compound), an + * update expression, or a `delete`. None of these can be replaced by a read + * of a cached local. + */ +function isWriteTarget(node) { + const parent = node.parent; + if (!parent) return false; + if (parent.type === "AssignmentExpression" && parent.left === node) return true; + if (parent.type === "UpdateExpression" && parent.argument === node) return true; + if (parent.type === "UnaryExpression" && parent.operator === "delete" && parent.argument === node) return true; + return false; +} + +/** + * True if `node` is the callee of a call/new/tagged-template. Caching a + * method in a local loses the receiver, so `obj.fn()` in the body is not + * something a simple `const fn = obj.fn` can replace. + */ +function isCallee(node) { + const parent = node.parent; + if (!parent) return false; + if ((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.callee === node) return true; + if (parent.type === "TaggedTemplateExpression" && parent.tag === node) return true; + return false; +} + +function skipKey(k) { + return k === "parent" || k === "type" || k === "loc" || k === "range" || k === "start" || k === "end"; +} + +/** + * Collect every simple static member-expression read inside the `if` test. + * Only the outermost chain is recorded (`a.b.c`, not also `a.b`). Callees and + * write targets are ignored: `if (obj.fn())` reads `obj.fn` but the value + * itself isn't something a local can reuse. + * + * A member expression that appears as the right-hand side of an assignment + * (`(local = obj.prop)`) is recorded in `cached` instead of `out`: that is + * the inline cache pattern this rule recommends, so a fallback + * `local ?? obj.prop` read in the body should not be flagged. + */ +function collectTestMembers(node, out, cached) { + if (!node || typeof node !== "object") return; + switch (node.type) { + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ClassDeclaration": + case "ClassExpression": + return; + case "MemberExpression": + if (!isCallee(node) && !isWriteTarget(node)) { + const key = memberExpressionKey(node); + if (key !== null) { + const parent = node.parent; + if (parent && parent.type === "AssignmentExpression" && parent.operator === "=" && parent.right === node) { + cached.add(key); + } else if (!out.has(key)) { + out.set(key, node); + } + return; + } + } + break; + } + for (const k in node) { + if (skipKey(k)) continue; + const v = node[k]; + if (Array.isArray(v)) { + for (const child of v) { + if (child && typeof child === "object") collectTestMembers(child, out, cached); + } + } else if (v && typeof v === "object" && typeof v.type === "string") { + collectTestMembers(v, out, cached); + } + } +} + +const READ = 1; +const WRITE = 2; +const CALLED = 4; + +/** + * Walk `node` collecting read/write/called flags for the static member + * expression identified by `key`. Does not descend into nested functions or + * classes: those run later with a different scope, so caching at the `if` + * wouldn't help (and the value may legitimately differ by then). + */ +function memberAccessFlags(node, key) { + if (!node || typeof node !== "object") return 0; + let flags = 0; + switch (node.type) { + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ClassDeclaration": + case "ClassExpression": + return 0; + case "MemberExpression": + if (memberExpressionKey(node) === key) { + if (isWriteTarget(node)) { + // Compound assignments (`+=`, `&&=`) and `++`/`--` also read the + // previous value, but the suggested refactor still can't + // eliminate the write-back, so treat them purely as writes here. + flags |= WRITE; + } else if (isCallee(node)) { + flags |= CALLED; + } else { + flags |= READ; + } + } + break; + } + for (const k in node) { + if (skipKey(k)) continue; + const v = node[k]; + if (Array.isArray(v)) { + for (const child of v) { + if (child && typeof child === "object") flags |= memberAccessFlags(child, key); + } + } else if (v && typeof v === "object" && typeof v.type === "string") { + flags |= memberAccessFlags(v, key); + } + } + return flags; +} + +const noDuplicateConditionalPropertyAccess = { + meta: { + type: "suggestion", + docs: { + description: + "Disallow reading the same property in an `if` condition and again in its body. " + + "Destructure or cache the property in a local first so the getter runs once.", + }, + messages: { + duplicate: + "`{{expr}}` is read in the `if` condition and again in the body. " + + "Read it into a local first (e.g. `const { {{prop}} } = {{base}}`) so the property is only accessed once.", + }, + schema: [], + }, + create(context) { + return { + IfStatement(node) { + const members = new Map(); + const cached = new Set(); + collectTestMembers(node.test, members, cached); + // A property already cached via `(local = obj.prop)` in the + // condition is the pattern this rule recommends; don't flag it. + for (const key of cached) members.delete(key); + if (members.size === 0) return; + + for (const [key, member] of members) { + const flags = memberAccessFlags(node.consequent, key); + // If the body writes to the same property, caching it in a local + // would change semantics (later reads would see the stale value). + if (flags & WRITE) continue; + // If the body calls it as a method, caching it in a local loses + // the receiver; the simple refactor doesn't apply. + if (flags & CALLED) continue; + if (!(flags & READ)) continue; + + const dot = key.lastIndexOf("."); + context.report({ + node: member, + messageId: "duplicate", + data: { + expr: key, + prop: key.slice(dot + 1), + base: key.slice(0, dot), + }, + }); + } + }, + }; + }, +}; + +export default { + meta: { + name: "bun", + }, + rules: { + "no-duplicate-conditional-property-access": noDuplicateConditionalPropertyAccess, + }, +}; diff --git a/src/js/builtins/BundlerPlugin.ts b/src/js/builtins/BundlerPlugin.ts index 94c5d8f7a6a..d980d60bf7c 100644 --- a/src/js/builtins/BundlerPlugin.ts +++ b/src/js/builtins/BundlerPlugin.ts @@ -378,8 +378,9 @@ export function runSetupFunction( setupResult = $peekPromiseSettledValue(setupResult); } else { return setupResult.$then(() => { - if (is_last && self.promises !== undefined && self.promises.length > 0) { - const awaitAll = Promise.all(self.promises); + let selfPromises; + if (is_last && (selfPromises = self.promises) !== undefined && selfPromises.length > 0) { + const awaitAll = Promise.all(selfPromises); return awaitAll.$then(processSetupResult); } return processSetupResult(); @@ -387,8 +388,9 @@ export function runSetupFunction( } } - if (is_last && this.promises !== undefined && this.promises.length > 0) { - const awaitAll = Promise.all(this.promises); + let pendingPromises; + if (is_last && (pendingPromises = this.promises) !== undefined && pendingPromises.length > 0) { + const awaitAll = Promise.all(pendingPromises); return awaitAll.$then(processSetupResult); } diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 26eac36ffc8..26bf0b01a19 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -13,9 +13,10 @@ export function from(value, encodingOrOffset, length) { return Buffer.from(valueOf, encodingOrOffset, length); } - if (value.length !== undefined || $inheritsArrayBuffer(value.buffer)) { - if (typeof value.length !== "number") return new $Buffer(0); - if (value.length <= 0) return new $Buffer(0); + const valueLength = value.length; + if (valueLength !== undefined || $inheritsArrayBuffer(value.buffer)) { + if (typeof valueLength !== "number") return new $Buffer(0); + if (valueLength <= 0) return new $Buffer(0); return new $Buffer(value); } const { type, data } = value; diff --git a/src/js/builtins/JSBufferPrototype.ts b/src/js/builtins/JSBufferPrototype.ts index af069645f57..a0449472ca0 100644 --- a/src/js/builtins/JSBufferPrototype.ts +++ b/src/js/builtins/JSBufferPrototype.ts @@ -97,8 +97,9 @@ export function readIntLE(this: BufferExt, offset, byteLength) { case 6: { if (typeof offset !== "number" || (offset | 0) !== offset) require("internal/validators").validateInteger(offset, "offset"); - if (!(offset >= 0 && offset <= this.length - byteLength)) - require("internal/buffer").boundsError(offset, this.length - byteLength); + let thisLength; + if (!(offset >= 0 && offset <= (thisLength = this.length) - byteLength)) + require("internal/buffer").boundsError(offset, (thisLength ?? this.length) - byteLength); } } switch (byteLength) { @@ -140,8 +141,9 @@ export function readIntBE(this: BufferExt, offset, byteLength) { case 6: { if (typeof offset !== "number" || (offset | 0) !== offset) require("internal/validators").validateInteger(offset, "offset"); - if (!(offset >= 0 && offset <= this.length - byteLength)) - require("internal/buffer").boundsError(offset, this.length - byteLength); + let thisLength; + if (!(offset >= 0 && offset <= (thisLength = this.length) - byteLength)) + require("internal/buffer").boundsError(offset, (thisLength ?? this.length) - byteLength); } } switch (byteLength) { @@ -183,8 +185,9 @@ export function readUIntLE(this: BufferExt, offset, byteLength) { case 6: { if (typeof offset !== "number" || (offset | 0) !== offset) require("internal/validators").validateInteger(offset, "offset"); - if (!(offset >= 0 && offset <= this.length - byteLength)) - require("internal/buffer").boundsError(offset, this.length - byteLength); + let thisLength; + if (!(offset >= 0 && offset <= (thisLength = this.length) - byteLength)) + require("internal/buffer").boundsError(offset, (thisLength ?? this.length) - byteLength); } } switch (byteLength) { @@ -223,8 +226,9 @@ export function readUIntBE(this: BufferExt, offset, byteLength) { case 6: { if (typeof offset !== "number" || (offset | 0) !== offset) require("internal/validators").validateInteger(offset, "offset"); - if (!(offset >= 0 && offset <= this.length - byteLength)) - require("internal/buffer").boundsError(offset, this.length - byteLength); + let thisLength; + if (!(offset >= 0 && offset <= (thisLength = this.length) - byteLength)) + require("internal/buffer").boundsError(offset, (thisLength ?? this.length) - byteLength); } } switch (byteLength) { diff --git a/src/js/builtins/ReadableByteStreamInternals.ts b/src/js/builtins/ReadableByteStreamInternals.ts index d74b854d86b..afb553c462b 100644 --- a/src/js/builtins/ReadableByteStreamInternals.ts +++ b/src/js/builtins/ReadableByteStreamInternals.ts @@ -600,7 +600,8 @@ export function readableByteStreamControllerPullInto(controller, view) { // of based on typed arrays private variables. However, this is not possible due // to bug 167697, which prevents access to typed arrays through their private // names unless public name has already been met before. - if (view.BYTES_PER_ELEMENT !== undefined) elementSize = view.BYTES_PER_ELEMENT; + const bytesPerElement = view.BYTES_PER_ELEMENT; + if (bytesPerElement !== undefined) elementSize = bytesPerElement; // FIXME: Getting constructor like this is not safe. A safe way of getting // it would be to determine which type of ArrayBufferView view is an instance diff --git a/src/js/builtins/ReadableStreamInternals.ts b/src/js/builtins/ReadableStreamInternals.ts index ec9dfffe54c..754d33d29cc 100644 --- a/src/js/builtins/ReadableStreamInternals.ts +++ b/src/js/builtins/ReadableStreamInternals.ts @@ -999,9 +999,11 @@ export function handleDirectStreamError(e) { this.error = this.flush = this.write = this.close = this.end = $onReadableStreamDirectControllerClosed; - if (typeof this.$underlyingSource.close === "function") { + const underlyingSource = this.$underlyingSource; + const underlyingClose = underlyingSource.close; + if (typeof underlyingClose === "function") { try { - this.$underlyingSource.close.$call(this.$underlyingSource, e); + underlyingClose.$call(underlyingSource, e); } catch {} } @@ -1156,9 +1158,11 @@ export function onCloseDirectStream(reason) { if (!sink) return; $putByIdDirectPrivate(stream, "state", $streamClosing); - if (typeof this.$underlyingSource.close === "function") { + const underlyingSource = this.$underlyingSource; + const underlyingClose = underlyingSource.close; + if (typeof underlyingClose === "function") { try { - this.$underlyingSource.close.$call(this.$underlyingSource, reason); + underlyingClose.$call(underlyingSource, reason); } catch {} } @@ -1640,9 +1644,10 @@ export function readableStreamDefaultControllerCancel(controller, reason) { export function readableStreamDefaultControllerPull(controller) { var queue = $getByIdDirectPrivate(controller, "queue"); - if (queue.content.isNotEmpty()) { + const content = queue.content; + if (content.isNotEmpty()) { const chunk = $dequeueValue(queue); - if ($getByIdDirectPrivate(controller, "closeRequested") && queue.content.isEmpty()) { + if ($getByIdDirectPrivate(controller, "closeRequested") && content.isEmpty()) { $readableStreamCloseIfPossible($getByIdDirectPrivate(controller, "controlledReadableStream")); } else $readableStreamDefaultControllerCallPullIfNeeded(controller); diff --git a/src/js/eval/feedback.ts b/src/js/eval/feedback.ts index 38f7b131619..ebd7c0f5bbd 100644 --- a/src/js/eval/feedback.ts +++ b/src/js/eval/feedback.ts @@ -210,9 +210,10 @@ async function promptForEmailInteractive(terminal: TerminalIO, defaultEmail?: st const render = () => { output.write(`\r\x1b[2K${symbols.question} ${bold}Email${reset}: `); - if (placeholderActive && placeholder.length > 0) { + let placeholderLength; + if (placeholderActive && (placeholderLength = placeholder.length) > 0) { output.write(`${dim}<${placeholder}>${reset}`); - output.write(`\x1b[${placeholder.length + 2}D`); + output.write(`\x1b[${placeholderLength + 2}D`); } else { output.write(value); } @@ -728,9 +729,10 @@ async function main() { body: form, }); - if (!response.ok || response.status !== 200) { + let status; + if (!response.ok || (status = response.status) !== 200) { const bodyText = await response.text().catch(() => ""); - logError(`Failed to send feedback (${response.status} ${response.statusText}).`); + logError(`Failed to send feedback (${status ?? response.status} ${response.statusText}).`); if (bodyText) { process.stderr.write(`${bodyText}\n`); } diff --git a/src/js/internal/assert/assertion_error.ts b/src/js/internal/assert/assertion_error.ts index 2465cfddf22..1890b932088 100644 --- a/src/js/internal/assert/assertion_error.ts +++ b/src/js/internal/assert/assertion_error.ts @@ -199,8 +199,9 @@ function createErrDiff(actual, expected, operator, customMessage) { if (showSimpleDiff) { const simpleDiff = getSimpleDiff(actual, inspectedSplitActual[0], expected, inspectedSplitExpected[0]); message = simpleDiff.message; - if (typeof simpleDiff.header !== "undefined") { - header = simpleDiff.header; + const simpleHeader = simpleDiff.header; + if (typeof simpleHeader !== "undefined") { + header = simpleHeader; } if (simpleDiff.skipped) { skipped = true; @@ -323,7 +324,8 @@ class AssertionError extends Error { // Only remove lines in case it makes sense to collapse those. // TODO: Accept env to always show the full error. - if (res.length > 50) { + const resLength = res.length; + if (resLength > 50) { res[46] = `${colors.blue}...${colors.white}`; while (res.length > 47) { ArrayPrototypePop.$call(res); diff --git a/src/js/internal/cluster/Worker.ts b/src/js/internal/cluster/Worker.ts index 71a991f7a8b..c63effb1db9 100644 --- a/src/js/internal/cluster/Worker.ts +++ b/src/js/internal/cluster/Worker.ts @@ -16,10 +16,11 @@ function Worker(options) { this.state = options.state || "none"; this.id = options.id | 0; - if (options.process) { - this.process = options.process; - this.process.on("error", (code, signal) => this.emit("error", code, signal)); - this.process.on("message", (message, handle) => this.emit("message", message, handle)); + const workerProcess = options.process; + if (workerProcess) { + this.process = workerProcess; + workerProcess.on("error", (code, signal) => this.emit("error", code, signal)); + workerProcess.on("message", (message, handle) => this.emit("message", message, handle)); } } $toClass(Worker, "Worker", EventEmitter); diff --git a/src/js/internal/cluster/child.ts b/src/js/internal/cluster/child.ts index 4d2c8897cdf..24c102da762 100644 --- a/src/js/internal/cluster/child.ts +++ b/src/js/internal/cluster/child.ts @@ -144,7 +144,8 @@ function shared(message, { handle, indexesKey, index }, cb) { // Round-robin. Master distributes handles across workers. function rr(message, { indexesKey, index }, cb) { - if (message.errno) return cb(message.errno, null); + const errno = message.errno; + if (errno) return cb(errno, null); let key = message.key; diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 46df6908d5a..cb4615db751 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -43,8 +43,9 @@ class SocketFramer { while (this.bufferedData.length > 0) { if (this.state === FramerState.WaitingForLength) { - if (this.sizeBufferIndex + this.bufferedData.length < 4) { - const remainingBytes = Math.min(4 - this.sizeBufferIndex, this.bufferedData.length); + const bufferedLength = this.bufferedData.length; + if (this.sizeBufferIndex + bufferedLength < 4) { + const remainingBytes = Math.min(4 - this.sizeBufferIndex, bufferedLength); this.bufferedData.copy(this.sizeBuffer, this.sizeBufferIndex, 0, remainingBytes); this.sizeBufferIndex += remainingBytes; this.bufferedData = this.bufferedData.slice(remainingBytes); @@ -119,8 +120,9 @@ export default function ( // If the user types --inspect, we print the URL to the console. // If the user is using an editor extension, don't print anything. if (!isAutomatic) { - if (debug.url) { - const { protocol, href, host, pathname } = debug.url; + const debugUrl = debug.url; + if (debugUrl) { + const { protocol, href, host, pathname } = debugUrl; if (!protocol.includes("unix")) { Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n"); Bun.write(Bun.stderr, `Listening:\n ${dim(href)}\n`); @@ -290,8 +292,9 @@ class Debugger { }, drain: _socket => {}, close: socket => { - if (socket.data) { - const { backend, framer } = socket.data; + const socketData = socket.data; + if (socketData) { + const { backend, framer } = socketData; backend.close(); framer.reset(); } @@ -496,8 +499,9 @@ async function connectToUnixServer( drain: _socket => {}, close: socket => { - if (socket.data) { - const { backend, framer } = socket.data; + const socketData = socket.data; + if (socketData) { + const { backend, framer } = socketData; backend.close(); framer.reset(); } diff --git a/src/js/internal/errors.ts b/src/js/internal/errors.ts index 50bb754512d..5433d9eb13d 100644 --- a/src/js/internal/errors.ts +++ b/src/js/internal/errors.ts @@ -5,9 +5,10 @@ const ArrayPrototypePush = Array.prototype.push; function aggregateTwoErrors(innerError: Error | undefined, outerError: Error & { errors?: Error[] }) { if (innerError && outerError && innerError !== outerError) { - if (ArrayIsArray(outerError.errors)) { + const outerErrors = outerError.errors; + if (ArrayIsArray(outerErrors)) { // If `outerError` is already an `AggregateError`. - ArrayPrototypePush.$call(outerError.errors, innerError); + ArrayPrototypePush.$call(outerErrors, innerError); return outerError; } const err = new AggregateError(new SafeArrayIterator([outerError, innerError]), outerError.message); diff --git a/src/js/internal/fifo.ts b/src/js/internal/fifo.ts index c9ead667060..56546308133 100644 --- a/src/js/internal/fifo.ts +++ b/src/js/internal/fifo.ts @@ -14,9 +14,11 @@ class Dequeue { } size(): number { - if (this._head === this._tail) return 0; - if (this._head < this._tail) return this._tail - this._head; - else return this._capacityMask + 1 - (this._head - this._tail); + const head = this._head; + const tail = this._tail; + if (head === tail) return 0; + if (head < tail) return tail - head; + else return this._capacityMask + 1 - (head - tail); } isEmpty(): boolean { @@ -54,10 +56,12 @@ class Dequeue { toArray(fullCopy: boolean): T[] { var list = this._list; var len = $toLength(list.length); + var head = this._head; + var tail = this._tail; - if (fullCopy || this._head > this._tail) { - var _head = $toLength(this._head); - var _tail = $toLength(this._tail); + if (fullCopy || head > tail) { + var _head = $toLength(head); + var _tail = $toLength(tail); var total = $toLength(len - _head + _tail); var array = $newArrayWithSize(total); var j = 0; @@ -65,7 +69,7 @@ class Dequeue { for (var i = 0; i < _tail; i++) $putByValDirect(array, j++, list[i]); return array as T[]; } else { - return slice.$call(list, this._head, this._tail); + return slice.$call(list, head, tail); } } diff --git a/src/js/internal/fs/cp-sync.ts b/src/js/internal/fs/cp-sync.ts index 731fb151328..b172d8c764c 100644 --- a/src/js/internal/fs/cp-sync.ts +++ b/src/js/internal/fs/cp-sync.ts @@ -39,16 +39,17 @@ const defaultCpOptions = { }; function decorateSystemError(err, prefix, context) { - let message = `${prefix}: ${context.syscall} returned ${context.code} (${context.message})`; - if (context.path !== undefined) message += ` ${context.path}`; - if (context.dest !== undefined) message += ` => ${context.dest}`; + const { syscall, code, message: ctxMessage, path, dest, errno } = context; + let message = `${prefix}: ${syscall} returned ${code} (${ctxMessage})`; + if (path !== undefined) message += ` ${path}`; + if (dest !== undefined) message += ` => ${dest}`; err.message = message; err.name = "SystemError"; err.info = context; - err.errno = context.errno; - err.syscall = context.syscall; - if (context.path !== undefined) err.path = context.path; - if (context.dest !== undefined) err.dest = context.dest; + err.errno = errno; + err.syscall = syscall; + if (path !== undefined) err.path = path; + if (dest !== undefined) err.dest = dest; return err; } @@ -133,8 +134,9 @@ function validateCpOptions(options) { 'Option "dereference" cannot be used in combination with option "verbatimSymlinks"', ); } - if (options.filter !== undefined) { - validateFunction(options.filter, "options.filter"); + const { filter } = options; + if (filter !== undefined) { + validateFunction(filter, "options.filter"); } options[kValidatedCpOptions] = true; return options; diff --git a/src/js/internal/fs/glob.ts b/src/js/internal/fs/glob.ts index a7bd5301351..c1fade7b884 100644 --- a/src/js/internal/fs/glob.ts +++ b/src/js/internal/fs/glob.ts @@ -1192,8 +1192,9 @@ function lazyMinimatch() { const isSequence = isNumericSequence || isAlphaSequence; const isOptions = m.body.indexOf(",") >= 0; if (!isSequence && !isOptions) { - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + "{" + m.body + escClose + m.post; + const mPost = m.post; + if (mPost.match(/,(?!,).*\}/)) { + str = m.pre + "{" + m.body + escClose + mPost; return expand_(str, max, true); } return [str]; @@ -1203,7 +1204,8 @@ function lazyMinimatch() { n = m.body.split(/\.\./); } else { n = parseCommaParts(m.body); - if (n.length === 1 && n[0] !== void 0) { + const nLength = n.length; + if (nLength === 1 && n[0] !== void 0) { n = expand_(n[0], max, false).map(embrace); if (n.length === 1) { return post.map(p => m.pre + n[0] + p); @@ -2653,29 +2655,31 @@ function lazyMinimatch() { const [head, body, tail] = partial ? [pattern.slice(patternIndex, firstgs), pattern.slice(firstgs + 1), []] : [pattern.slice(patternIndex, firstgs), pattern.slice(firstgs + 1, lastgs), pattern.slice(lastgs + 1)]; - if (head.length) { - const fileHead = file.slice(fileIndex, fileIndex + head.length); + const headLength = head.length; + if (headLength) { + const fileHead = file.slice(fileIndex, fileIndex + headLength); if (!this.#matchOne(fileHead, head, partial, 0, 0)) { return false; } - fileIndex += head.length; - patternIndex += head.length; + fileIndex += headLength; + patternIndex += headLength; } let fileTailMatch = 0; - if (tail.length) { - if (tail.length + fileIndex > file.length) return false; - let tailStart = file.length - tail.length; + const tailLength = tail.length; + if (tailLength) { + if (tailLength + fileIndex > file.length) return false; + let tailStart = file.length - tailLength; if (this.#matchOne(file, tail, partial, tailStart, 0)) { - fileTailMatch = tail.length; + fileTailMatch = tailLength; } else { - if (file[file.length - 1] !== "" || fileIndex + tail.length === file.length) { + if (file[file.length - 1] !== "" || fileIndex + tailLength === file.length) { return false; } tailStart--; if (!this.#matchOne(file, tail, partial, tailStart, 0)) { return false; } - fileTailMatch = tail.length + 1; + fileTailMatch = tailLength + 1; } } if (!body.length) { @@ -2826,7 +2830,8 @@ function lazyMinimatch() { return re; } makeRe() { - if (this.regexp || this.regexp === false) return this.regexp; + const regexp = this.regexp; + if (regexp || regexp === false) return regexp; const set = this.set; if (!set.length) { this.regexp = false; @@ -2857,15 +2862,19 @@ function lazyMinimatch() { } } else if (next === void 0) { pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + ")?"; - } else if (next !== exports.GLOBSTAR) { - pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + "\\/)" + next; - pp[i + 1] = exports.GLOBSTAR; + } else { + const GLOBSTAR = exports.GLOBSTAR; + if (next !== GLOBSTAR) { + pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + "\\/)" + next; + pp[i + 1] = GLOBSTAR; + } } }); const filtered = pp.filter(p => p !== exports.GLOBSTAR); - if (this.partial && filtered.length >= 1) { + let filteredLength; + if (this.partial && (filteredLength = filtered.length) >= 1) { const prefixes = []; - for (let i = 1; i <= filtered.length; i++) { + for (let i = 1; i <= filteredLength; i++) { prefixes.push(filtered.slice(0, i).join("/")); } return "(?:" + prefixes.join("|") + ")"; diff --git a/src/js/internal/fs/streams.ts b/src/js/internal/fs/streams.ts index b4943656a3f..bc94cee351c 100644 --- a/src/js/internal/fs/streams.ts +++ b/src/js/internal/fs/streams.ts @@ -503,8 +503,9 @@ function WriteStream(this: FSStream, path: string | null, options?: any): void { Writable.$call(this, options); - if (options.encoding) { - this.setDefaultEncoding(options.encoding); + const encoding = options.encoding; + if (encoding) { + this.setDefaultEncoding(encoding); } return this as unknown as void; } diff --git a/src/js/internal/fs/watch.ts b/src/js/internal/fs/watch.ts index 0b748ab51f1..5436d588a98 100644 --- a/src/js/internal/fs/watch.ts +++ b/src/js/internal/fs/watch.ts @@ -165,12 +165,14 @@ class FSWatcher extends EventEmitter { ref() { // like node, honour a replaced _handle and support chaining - if (this._handle) this._handle.ref(); + const handle = this._handle; + if (handle) handle.ref(); return this; } unref() { - if (this._handle) this._handle.unref(); + const handle = this._handle; + if (handle) handle.unref(); return this; } diff --git a/src/js/internal/linkedlist.ts b/src/js/internal/linkedlist.ts index b5ed1b4246a..bbfbc950e36 100644 --- a/src/js/internal/linkedlist.ts +++ b/src/js/internal/linkedlist.ts @@ -12,12 +12,14 @@ export function peek(list) { // Remove an item from its list. export function remove(item) { - if (item._idleNext) { - item._idleNext._idlePrev = item._idlePrev; + const next = item._idleNext; + const prev = item._idlePrev; + if (next) { + next._idlePrev = prev; } - if (item._idlePrev) { - item._idlePrev._idleNext = item._idleNext; + if (prev) { + prev._idleNext = next; } item._idleNext = null; diff --git a/src/js/internal/primordials.js b/src/js/internal/primordials.js index 9ad7d3e8001..e8be2528d21 100644 --- a/src/js/internal/primordials.js +++ b/src/js/internal/primordials.js @@ -45,13 +45,15 @@ const copyProps = (src, dest) => { }; const makeSafe = (unsafe, safe) => { - if (Symbol.iterator in unsafe.prototype) { + const unsafePrototype = unsafe.prototype; + const safePrototype = safe.prototype; + if (Symbol.iterator in unsafePrototype) { const dummy = new unsafe(); let next; // We can reuse the same `next` method. - ArrayPrototypeForEach(Reflect.ownKeys(unsafe.prototype), key => { - if (!Reflect.getOwnPropertyDescriptor(safe.prototype, key)) { - const desc = Reflect.getOwnPropertyDescriptor(unsafe.prototype, key); + ArrayPrototypeForEach(Reflect.ownKeys(unsafePrototype), key => { + if (!Reflect.getOwnPropertyDescriptor(safePrototype, key)) { + const desc = Reflect.getOwnPropertyDescriptor(unsafePrototype, key); if (typeof desc.value === "function" && desc.value.length === 0) { const called = desc.value.$call(dummy) || {}; if (Symbol.iterator in (typeof called === "object" ? called : {})) { @@ -63,14 +65,14 @@ const makeSafe = (unsafe, safe) => { }; } } - Reflect.defineProperty(safe.prototype, key, desc); + Reflect.defineProperty(safePrototype, key, desc); } }); - } else copyProps(unsafe.prototype, safe.prototype); + } else copyProps(unsafePrototype, safePrototype); copyProps(unsafe, safe); - Object.setPrototypeOf(safe.prototype, null); - Object.freeze(safe.prototype); + Object.setPrototypeOf(safePrototype, null); + Object.freeze(safePrototype); Object.freeze(safe); return safe; }; diff --git a/src/js/internal/sql/errors.ts b/src/js/internal/sql/errors.ts index ba2f70c4423..b287e556e83 100644 --- a/src/js/internal/sql/errors.ts +++ b/src/js/internal/sql/errors.ts @@ -52,24 +52,43 @@ class PostgresError extends SQLError implements Bun.SQL.PostgresError { super(message); this.name = "PostgresError"; - this.code = options.code; - - if (options.errno !== undefined) this.errno = options.errno; - if (options.detail !== undefined) this.detail = options.detail; - if (options.hint !== undefined) this.hint = options.hint; - if (options.severity !== undefined) this.severity = options.severity; - if (options.position !== undefined) this.position = options.position; - if (options.internalPosition !== undefined) this.internalPosition = options.internalPosition; - if (options.internalQuery !== undefined) this.internalQuery = options.internalQuery; - if (options.where !== undefined) this.where = options.where; - if (options.schema !== undefined) this.schema = options.schema; - if (options.table !== undefined) this.table = options.table; - if (options.column !== undefined) this.column = options.column; - if (options.dataType !== undefined) this.dataType = options.dataType; - if (options.constraint !== undefined) this.constraint = options.constraint; - if (options.file !== undefined) this.file = options.file; - if (options.line !== undefined) this.line = options.line; - if (options.routine !== undefined) this.routine = options.routine; + const { + code, + errno, + detail, + hint, + severity, + position, + internalPosition, + internalQuery, + where, + schema, + table, + column, + dataType, + constraint, + file, + line, + routine, + } = options; + this.code = code; + + if (errno !== undefined) this.errno = errno; + if (detail !== undefined) this.detail = detail; + if (hint !== undefined) this.hint = hint; + if (severity !== undefined) this.severity = severity; + if (position !== undefined) this.position = position; + if (internalPosition !== undefined) this.internalPosition = internalPosition; + if (internalQuery !== undefined) this.internalQuery = internalQuery; + if (where !== undefined) this.where = where; + if (schema !== undefined) this.schema = schema; + if (table !== undefined) this.table = table; + if (column !== undefined) this.column = column; + if (dataType !== undefined) this.dataType = dataType; + if (constraint !== undefined) this.constraint = constraint; + if (file !== undefined) this.file = file; + if (line !== undefined) this.line = line; + if (routine !== undefined) this.routine = routine; } } @@ -93,10 +112,11 @@ class SQLiteError extends SQLError implements Bun.SQL.SQLiteError { this.name = "SQLiteError"; - this.code = options.code; - this.errno = options.errno; + const { code, errno, byteOffset } = options; + this.code = code; + this.errno = errno; - if (options.byteOffset !== undefined) this.byteOffset = options.byteOffset; + if (byteOffset !== undefined) this.byteOffset = byteOffset; } } @@ -119,10 +139,11 @@ class MySQLError extends SQLError implements Bun.SQL.MySQLError { super(message); this.name = "MySQLError"; - this.code = options.code; + const { code, errno, sqlState } = options; + this.code = code; - if (options.errno !== undefined) this.errno = options.errno; - if (options.sqlState !== undefined) this.sqlState = options.sqlState; + if (errno !== undefined) this.errno = errno; + if (sqlState !== undefined) this.sqlState = sqlState; } } diff --git a/src/js/internal/sql/shared.ts b/src/js/internal/sql/shared.ts index 0f8ca854739..8fe68add1b4 100644 --- a/src/js/internal/sql/shared.ts +++ b/src/js/internal/sql/shared.ts @@ -991,8 +991,9 @@ abstract class BaseSQLAdapter void): void { - if (connection.queries) { - connection.queries.delete(handler); + const queries = connection.queries; + if (queries) { + queries.delete(handler); } } @@ -1069,7 +1070,8 @@ abstract class BaseSQLAdapter { // Keep it because cleanup removes it. const endCallback = callback; cleanup(); - endCallback.$call(stream, $makeAbortError(undefined, { cause: options.signal.reason })); + endCallback.$call(stream, $makeAbortError(undefined, { cause: signal.reason })); }; addAbortListener ??= require("internal/abort_listener").addAbortListener; - const disposable = addAbortListener(options.signal, abort); + const disposable = addAbortListener(signal, abort); const originalCallback = callback; callback = once((...args) => { disposable[SymbolDispose](); @@ -318,16 +323,17 @@ function eosWeb(stream, options, callback) { let isAborted = false; let abort = nop; - if (options.signal) { + const signal = options.signal; + if (signal) { abort = () => { isAborted = true; - callback.$call(stream, $makeAbortError(undefined, { cause: options.signal.reason })); + callback.$call(stream, $makeAbortError(undefined, { cause: signal.reason })); }; - if (options.signal.aborted) { + if (signal.aborted) { process.nextTick(abort); } else { addAbortListener ??= require("internal/abort_listener").addAbortListener; - const disposable = addAbortListener(options.signal, abort); + const disposable = addAbortListener(signal, abort); const originalCallback = callback; callback = once((...args) => { disposable[SymbolDispose](); diff --git a/src/js/internal/streams/iter/broadcast.ts b/src/js/internal/streams/iter/broadcast.ts index 80b763bf23a..fd11ae7feaa 100644 --- a/src/js/internal/streams/iter/broadcast.ts +++ b/src/js/internal/streams/iter/broadcast.ts @@ -586,9 +586,10 @@ class BroadcastWriter { #resolvePendingWrites() { while (this.#pendingWrites.length > 0 && this.#broadcast[kCanWrite]()) { const pending = this.#pendingWrites.shift(); - if (this.#broadcast[kWrite](pending.chunk)) { - for (let i = 0; i < pending.chunk.length; i++) { - this.#totalBytes += pending.chunk[i].byteLength; + const chunk = pending.chunk; + if (this.#broadcast[kWrite](chunk)) { + for (let i = 0; i < chunk.length; i++) { + this.#totalBytes += chunk[i].byteLength; } pending.resolve(); } else { diff --git a/src/js/internal/streams/iter/consumers.ts b/src/js/internal/streams/iter/consumers.ts index e64643b5833..4d81de6ca6a 100644 --- a/src/js/internal/streams/iter/consumers.ts +++ b/src/js/internal/streams/iter/consumers.ts @@ -140,25 +140,28 @@ function toArrayBuffer(data) { function validateBaseConsumerOptions(options) { validateObject(options, "options"); - if (options.limit !== undefined) { - validateInteger(options.limit, "options.limit", 0); + const limit = options.limit; + if (limit !== undefined) { + validateInteger(limit, "options.limit", 0); } - if (options.encoding !== undefined) { - if (typeof options.encoding !== "string") { - throw $ERR_INVALID_ARG_TYPE("options.encoding", "string", options.encoding); + const encoding = options.encoding; + if (encoding !== undefined) { + if (typeof encoding !== "string") { + throw $ERR_INVALID_ARG_TYPE("options.encoding", "string", encoding); } try { - new TextDecoder(options.encoding); + new TextDecoder(encoding); } catch { - throw $ERR_INVALID_ARG_VALUE_RangeError("options.encoding", options.encoding); + throw $ERR_INVALID_ARG_VALUE_RangeError("options.encoding", encoding); } } } function validateConsumerOptions(options) { validateBaseConsumerOptions(options); - if (options.signal !== undefined) { - validateAbortSignal(options.signal, "options.signal"); + const { signal } = options; + if (signal !== undefined) { + validateAbortSignal(signal, "options.signal"); } } @@ -341,8 +344,9 @@ function merge(...args) { let sources; let options; - if (args.length > 0 && isMergeOptions(args[args.length - 1])) { - options = args[args.length - 1]; + const argc = args.length; + if (argc > 0 && isMergeOptions(args[argc - 1])) { + options = args[argc - 1]; sources = args.slice(0, -1); } else { sources = args; diff --git a/src/js/internal/streams/iter/pull.ts b/src/js/internal/streams/iter/pull.ts index 068ce74b9f3..01e253b2503 100644 --- a/src/js/internal/streams/iter/pull.ts +++ b/src/js/internal/streams/iter/pull.ts @@ -759,13 +759,14 @@ function pipeToSync(source, ...args) { try { for (const batch of pipeline) { - if (hasWritevSync && batch.length > 1) { + let batchLength; + if (hasWritevSync && (batchLength = batch.length) > 1) { writer.writevSync(batch); - for (let i = 0; i < batch.length; i++) { + for (let i = 0; i < batchLength; i++) { totalBytes += batch[i].byteLength; } } else { - for (let i = 0; i < batch.length; i++) { + for (let i = 0; i < (batchLength ??= batch.length); i++) { const chunk = batch[i]; writer.writeSync(chunk); totalBytes += chunk.byteLength; @@ -859,10 +860,11 @@ async function pipeTo(source, ...args) { // Returns undefined on sync success, or a Promise when async fallback // is required. Callers must check: const p = writeBatch(b); if (p) await p; function writeBatch(batch) { - if (hasWritev && batch.length > 1) { + let batchLength; + if (hasWritev && (batchLength = batch.length) > 1) { if (!hasWritevSync || !writer.writevSync(batch)) { if (hasWritevSync && syncFalseWasAccepted()) { - for (let i = 0; i < batch.length; i++) { + for (let i = 0; i < batchLength; i++) { totalBytes += batch[i].byteLength; } return waitForSyncBackpressure(); diff --git a/src/js/internal/streams/iter/push.ts b/src/js/internal/streams/iter/push.ts index ae1c19df15f..cb355173acf 100644 --- a/src/js/internal/streams/iter/push.ts +++ b/src/js/internal/streams/iter/push.ts @@ -709,8 +709,9 @@ function push(...args) { // Apply transforms lazily if provided let readable; if (transforms.length > 0) { - if (options.signal) { - readable = pullWithTransforms(rawReadable, ...transforms, { __proto__: null, signal: options.signal }); + const signal = options.signal; + if (signal) { + readable = pullWithTransforms(rawReadable, ...transforms, { __proto__: null, signal }); } else { readable = pullWithTransforms(rawReadable, ...transforms); } diff --git a/src/js/internal/streams/iter/transform.ts b/src/js/internal/streams/iter/transform.ts index c28dc3b15ab..bada684f555 100644 --- a/src/js/internal/streams/iter/transform.ts +++ b/src/js/internal/streams/iter/transform.ts @@ -200,9 +200,9 @@ function createBrotliHandle(mode, options, processCallback, onError) { brotliInitParamsArray[constants.BROTLI_PARAM_QUALITY] = 6; brotliInitParamsArray[constants.BROTLI_PARAM_LGWIN] = 20; } - if (options.params) { + const params = options.params; + if (params) { // User-supplied params override the defaults above. - const params = options.params; const keys = Object.keys(params); for (let i = 0; i < keys.length; i++) { const key = +keys[i]; @@ -248,8 +248,8 @@ function createZstdHandle(mode, options, processCallback, onError) { const initArray = isCompress ? zstdInitCParamsArray : zstdInitDParamsArray; initArray.fill(-1); - if (options.params) { - const params = options.params; + const params = options.params; + if (params) { const keys = Object.keys(params); for (let i = 0; i < keys.length; i++) { const key = +keys[i]; diff --git a/src/js/internal/streams/iter/utils.ts b/src/js/internal/streams/iter/utils.ts index de17ea1ad74..c3c4a822f84 100644 --- a/src/js/internal/streams/iter/utils.ts +++ b/src/js/internal/streams/iter/utils.ts @@ -57,10 +57,11 @@ function getMinCursor(consumers, fallback) { let minCursor = fallback; let minCursorConsumers = 0; for (const consumer of consumers) { - if (consumer.cursor < minCursor) { - minCursor = consumer.cursor; + const cursor = consumer.cursor; + if (cursor < minCursor) { + minCursor = cursor; minCursorConsumers = 1; - } else if (consumer.cursor === minCursor) { + } else if (cursor === minCursor) { minCursorConsumers++; } } diff --git a/src/js/internal/streams/legacy.ts b/src/js/internal/streams/legacy.ts index 005a6f9342f..a5235620e2c 100644 --- a/src/js/internal/streams/legacy.ts +++ b/src/js/internal/streams/legacy.ts @@ -109,9 +109,10 @@ function prependListener(emitter, event, fn) { // userland ones. NEVER DO THIS. This is here only because this code needs // to continue to work with older versions of Node.js that do not include // the prependListener() method. The goal is to eventually remove this hack. - if (!emitter._events || !emitter._events[event]) emitter.on(event, fn); - else if (ArrayIsArray(emitter._events[event])) emitter._events[event].unshift(fn); - else emitter._events[event] = [fn, emitter._events[event]]; + let events, existing; + if (!(events = emitter._events) || !(existing = events[event])) emitter.on(event, fn); + else if (ArrayIsArray(existing)) existing.unshift(fn); + else events[event] = [fn, existing]; } // Add helper methods to Stream diff --git a/src/js/internal/streams/native-readable.ts b/src/js/internal/streams/native-readable.ts index e742266ac6e..2cefb1c3cdd 100644 --- a/src/js/internal/streams/native-readable.ts +++ b/src/js/internal/streams/native-readable.ts @@ -68,11 +68,8 @@ function constructNativeReadable(readableStream: ReadableStream, options): Nativ stream[kHasResized] = !dynamicallyAdjustChunkSize(); stream[kCloseState] = [false]; - if (typeof options.highWaterMark === "number") { - stream[kHighWaterMark] = options.highWaterMark; - } else { - stream[kHighWaterMark] = 256 * 1024; - } + const highWaterMark = options.highWaterMark; + stream[kHighWaterMark] = typeof highWaterMark === "number" ? highWaterMark : 256 * 1024; stream.ref = ref; stream.unref = unref; diff --git a/src/js/internal/streams/readable.ts b/src/js/internal/streams/readable.ts index 46a834f1c49..6d4839ebbd5 100644 --- a/src/js/internal/streams/readable.ts +++ b/src/js/internal/streams/readable.ts @@ -280,13 +280,14 @@ function Readable(options): void { this._readableState = new ReadableState(options, this, false); if (options) { - if (typeof options.read === "function") this._read = options.read; + const { read, destroy, construct, signal } = options; + if (typeof read === "function") this._read = read; - if (typeof options.destroy === "function") this._destroy = options.destroy; + if (typeof destroy === "function") this._destroy = destroy; - if (typeof options.construct === "function") this._construct = options.construct; + if (typeof construct === "function") this._construct = construct; - if (options.signal) addAbortSignal(options.signal, this); + if (signal) addAbortSignal(signal, this); } Stream.$call(this, options); @@ -352,11 +353,12 @@ function readableAddChunkUnshiftByteMode(stream, state, chunk, encoding) { if (typeof chunk === "string") { encoding ||= state.defaultEncoding; - if (state.encoding !== encoding) { - if (state.encoding) { + const stateEncoding = state.encoding; + if (stateEncoding !== encoding) { + if (stateEncoding) { // When unshifting, if state.encoding is set, we have to save // the string in the BufferList with the state encoding. - chunk = Buffer.from(chunk, encoding).toString(state.encoding); + chunk = Buffer.from(chunk, encoding).toString(stateEncoding); } else { chunk = Buffer.from(chunk, encoding); } @@ -594,14 +596,15 @@ Readable.prototype.read = function (n) { // If we're doing read(0) to trigger a readable event, but we // already have a bunch of data in the buffer, then just trigger // the 'readable' event and move on. + const stateLength = state.length; if ( n === 0 && (state[kState] & kNeedReadable) !== 0 && - ((state.highWaterMark !== 0 ? state.length >= state.highWaterMark : state.length > 0) || + ((state.highWaterMark !== 0 ? stateLength >= state.highWaterMark : stateLength > 0) || (state[kState] & kEnded) !== 0) ) { $debug("read: emitReadable"); - if (state.length === 0 && (state[kState] & kEnded) !== 0) endReadable(this); + if (stateLength === 0 && (state[kState] & kEnded) !== 0) endReadable(this); else emitReadable(this); return null; } @@ -1382,8 +1385,9 @@ ObjectDefineProperties(Readable.prototype, { }, set(val) { // Backwards compat. - if (this._readableState) { - this._readableState.readable = !!val; + const state = this._readableState; + if (state) { + state.readable = !!val; } }, }, @@ -1431,8 +1435,9 @@ ObjectDefineProperties(Readable.prototype, { return this._readableState.flowing; }, set: function (state) { - if (this._readableState) { - this._readableState.flowing = state; + const readableState = this._readableState; + if (readableState) { + readableState.flowing = state; } }, }, @@ -1547,10 +1552,11 @@ function fromList(n, state) { const buf = state.buffer; const len = buf.length; + let stateLength; if ((state[kState] & kObjectMode) !== 0) { ret = buf[idx]; buf[idx++] = null; - } else if (!n || n >= state.length) { + } else if (!n || n >= (stateLength = state.length)) { // Read it all, truncate the list. if ((state[kState] & kDecoder) !== 0) { ret = ""; @@ -1564,7 +1570,7 @@ function fromList(n, state) { ret = buf[idx]; buf[idx++] = null; } else { - ret = Buffer.allocUnsafe(state.length); + ret = Buffer.allocUnsafe(stateLength ?? state.length); let i = 0; while (idx < len) { @@ -1585,12 +1591,13 @@ function fromList(n, state) { ret = ""; while (idx < len) { const str = buf[idx]; - if (n > str.length) { + const strLength = str.length; + if (n > strLength) { ret += str; - n -= str.length; + n -= strLength; buf[idx++] = null; } else { - if (n === str.length) { + if (n === strLength) { ret += str; buf[idx++] = null; } else { @@ -1606,17 +1613,18 @@ function fromList(n, state) { const retLen = n; while (idx < len) { const data = buf[idx]; - if (n > data.length) { + const dataLength = data.length; + if (n > dataLength) { TypedArrayPrototypeSet.$call(ret, data, retLen - n); - n -= data.length; + n -= dataLength; buf[idx++] = null; } else { - if (n === data.length) { + if (n === dataLength) { TypedArrayPrototypeSet.$call(ret, data, retLen - n); buf[idx++] = null; } else { TypedArrayPrototypeSet.$call(ret, new $Buffer(data.buffer, data.byteOffset, n), retLen - n); - buf[idx] = new $Buffer(data.buffer, data.byteOffset + n, data.length - n); + buf[idx] = new $Buffer(data.buffer, data.byteOffset + n, dataLength - n); } break; } diff --git a/src/js/internal/streams/transform.ts b/src/js/internal/streams/transform.ts index 7207a701362..3c2b93a7e0f 100644 --- a/src/js/internal/streams/transform.ts +++ b/src/js/internal/streams/transform.ts @@ -76,9 +76,10 @@ function Transform(options): void { this[kCallback] = null; if (options) { - if (typeof options.transform === "function") this._transform = options.transform; + const { transform, flush } = options; + if (typeof transform === "function") this._transform = transform; - if (typeof options.flush === "function") this._flush = options.flush; + if (typeof flush === "function") this._flush = flush; } // When the writable side finishes, then flush out anything remaining. diff --git a/src/js/internal/streams/utils.ts b/src/js/internal/streams/utils.ts index 3e11425e08a..e4bc3915bce 100644 --- a/src/js/internal/streams/utils.ts +++ b/src/js/internal/streams/utils.ts @@ -183,8 +183,9 @@ function isWritableErrored(stream) { return null; } - if (stream.writableErrored) { - return stream.writableErrored; + const writableErrored = stream.writableErrored; + if (writableErrored) { + return writableErrored; } return stream._writableState?.errored ?? null; @@ -195,8 +196,9 @@ function isReadableErrored(stream) { return null; } - if (stream.readableErrored) { - return stream.readableErrored; + const readableErrored = stream.readableErrored; + if (readableErrored) { + return readableErrored; } return stream._readableState?.errored ?? null; @@ -207,8 +209,9 @@ function isClosed(stream) { return null; } - if (typeof stream.closed === "boolean") { - return stream.closed; + const closed = stream.closed; + if (typeof closed === "boolean") { + return closed; } const wState = stream._writableState; @@ -218,8 +221,9 @@ function isClosed(stream) { return wState?.closed || rState?.closed; } - if (typeof stream._closed === "boolean" && isOutgoingMessage(stream)) { - return stream._closed; + const _closed = stream._closed; + if (typeof _closed === "boolean" && isOutgoingMessage(stream)) { + return _closed; } return null; diff --git a/src/js/internal/streams/writable.ts b/src/js/internal/streams/writable.ts index af5d7b7d4f5..d0fe11d84d4 100644 --- a/src/js/internal/streams/writable.ts +++ b/src/js/internal/streams/writable.ts @@ -372,17 +372,18 @@ function Writable(options): void { this._writableState = new WritableState(options, this, false); if (options) { - if (typeof options.write === "function") this._write = options.write; + const { write, writev, destroy, final, construct, signal } = options; + if (typeof write === "function") this._write = write; - if (typeof options.writev === "function") this._writev = options.writev; + if (typeof writev === "function") this._writev = writev; - if (typeof options.destroy === "function") this._destroy = options.destroy; + if (typeof destroy === "function") this._destroy = destroy; - if (typeof options.final === "function") this._final = options.final; + if (typeof final === "function") this._final = final; - if (typeof options.construct === "function") this._construct = options.construct; + if (typeof construct === "function") this._construct = construct; - if (options.signal) addAbortSignal(options.signal, this); + if (signal) addAbortSignal(signal, this); } Stream.$call(this, options); @@ -597,8 +598,9 @@ function onwrite(stream, er) { // In case of duplex streams we need to notify the readable side of the // error. - if (stream._readableState && !stream._readableState.errored) { - stream._readableState.errored = er; + const readableState = stream._readableState; + if (readableState && !readableState.errored) { + readableState.errored = er; } if (sync) { @@ -982,8 +984,9 @@ ObjectDefineProperties(Writable.prototype, { }, set(val) { // Backwards compatible. - if (this._writableState) { - this._writableState.writable = !!val; + const state = this._writableState; + if (state) { + state.writable = !!val; } }, }, diff --git a/src/js/internal/url.ts b/src/js/internal/url.ts index a863cd71857..c29a0e4241e 100644 --- a/src/js/internal/url.ts +++ b/src/js/internal/url.ts @@ -10,11 +10,14 @@ function urlToHttpOptions(url) { path: `${url.pathname || ""}${url.search || ""}`, href: url.href, }; - if (url.port !== "") { - options.port = Number(url.port); + const port = url.port; + if (port !== "") { + options.port = Number(port); } - if (url.username || url.password) { - options.auth = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`; + const username = url.username; + let password; + if (username || (password = url.password)) { + options.auth = `${decodeURIComponent(username)}:${decodeURIComponent(password ?? url.password)}`; } return options; } diff --git a/src/js/internal/util/inspect.js b/src/js/internal/util/inspect.js index 70fe0f597b8..d531a40b744 100644 --- a/src/js/internal/util/inspect.js +++ b/src/js/internal/util/inspect.js @@ -273,23 +273,26 @@ const codes = {}; // exported from errors.js ArrayPrototypePush.$call(instances, "Object"); } } - if (types.length > 0) { - if (types.length > 2) msg += `one of type ${ArrayPrototypeJoin(types, ", ")}, or ${ArrayPrototypePop(types)}`; - else if (types.length === 2) msg += `one of type ${types[0]} or ${types[1]}`; + const typesLength = types.length; + if (typesLength > 0) { + if (typesLength > 2) msg += `one of type ${ArrayPrototypeJoin(types, ", ")}, or ${ArrayPrototypePop(types)}`; + else if (typesLength === 2) msg += `one of type ${types[0]} or ${types[1]}`; else msg += `of type ${types[0]}`; if (instances.length > 0 || other.length > 0) msg += " or "; } - if (instances.length > 0) { - if (instances.length > 2) + const instancesLength = instances.length; + if (instancesLength > 0) { + if (instancesLength > 2) msg += `an instance of ${ArrayPrototypeJoin(instances, ", ")}, or ${ArrayPrototypePop(instances)}`; - else msg += `an instance of ${instances[0]}` + (instances.length === 2 ? ` or ${instances[1]}` : ""); + else msg += `an instance of ${instances[0]}` + (instancesLength === 2 ? ` or ${instances[1]}` : ""); if (other.length > 0) msg += " or "; } - if (other.length > 0) { - if (other.length > 2) { + const otherLength = other.length; + if (otherLength > 0) { + if (otherLength > 2) { const last = ArrayPrototypePop(other); msg += `one of ${ArrayPrototypeJoin(other, ", ")}, or ${last}`; - } else if (other.length === 2) { + } else if (otherLength === 2) { msg += `one of ${other[0]} or ${other[1]}`; } else { if (StringPrototypeToLowerCase(other[0]) !== other[0]) msg += "an "; @@ -297,10 +300,13 @@ const codes = {}; // exported from errors.js } } + let actualName; if (actual == null) msg += `. Received ${actual}`; - else if (typeof actual === "function" && actual.name) msg += `. Received function ${actual.name}`; + else if (typeof actual === "function" && (actualName = actual.name)) msg += `. Received function ${actualName}`; else if (typeof actual === "object") { - if (actual.constructor && actual.constructor.name) msg += `. Received an instance of ${actual.constructor.name}`; + const actualCtor = actual.constructor; + const actualCtorName = actualCtor ? actualCtor.name : undefined; + if (actualCtorName) msg += `. Received an instance of ${actualCtorName}`; else msg += `. Received ${inspect(actual, { depth: -1 })}`; } else { let inspected = inspect(actual, { colors: false }); @@ -711,13 +717,14 @@ function inspect(value, opts) { getters: inspectDefaultOptions.getters, numericSeparator: inspectDefaultOptions.numericSeparator, }; - if (arguments.length > 1) { + const argc = arguments.length; + if (argc > 1) { // Legacy... - if (arguments.length > 2) { + if (argc > 2) { if (arguments[2] !== undefined) { ctx.depth = arguments[2]; } - if (arguments.length > 3 && arguments[3] !== undefined) { + if (argc > 3 && arguments[3] !== undefined) { ctx.colors = arguments[3]; } } @@ -1000,16 +1007,15 @@ function getConstructorName(obj, ctx, recurseTimes, protoProps) { } } const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor"); - if ( - descriptor !== undefined && - typeof descriptor.value === "function" && - descriptor.value.name !== "" && - isInstanceof(tmp, descriptor.value) - ) { - if (protoProps !== undefined && (firstProto !== obj || !builtInObjects.has(descriptor.value.name))) { - addPrototypeProperties(ctx, tmp, firstProto || tmp, recurseTimes, protoProps); + const descriptorValue = descriptor !== undefined ? descriptor.value : undefined; + if (typeof descriptorValue === "function") { + const descriptorValueName = descriptorValue.name; + if (descriptorValueName !== "" && isInstanceof(tmp, descriptorValue)) { + if (protoProps !== undefined && (firstProto !== obj || !builtInObjects.has(descriptorValueName))) { + addPrototypeProperties(ctx, tmp, firstProto || tmp, recurseTimes, protoProps); + } + return String(descriptorValueName); } - return String(descriptor.value.name); } obj = ObjectGetPrototypeOf(obj); @@ -1450,8 +1456,9 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { } throw new AssertionError("handleMaxCallStackSize assertion failed: " + String(err), true); } - if (ctx.circular !== undefined) { - const index = ctx.circular.get(value); + const circular = ctx.circular; + if (circular !== undefined) { + const index = circular.get(value); if (index !== undefined) { ctx.seenRefs ??= new Set(); const SEEN = ctx.seenRefs.has(index); @@ -1476,14 +1483,18 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { } ctx.seen.pop(); - if (ctx.sorted) { - const comparator = ctx.sorted === true ? undefined : ctx.sorted; + const ctxSorted = ctx.sorted; + if (ctxSorted) { + const comparator = ctxSorted === true ? undefined : ctxSorted; if (extrasType === kObjectType) { ArrayPrototypeSort(output, comparator); - } else if (keys.length > 1) { - const sorted = ArrayPrototypeSort(ArrayPrototypeSlice(output, output.length - keys.length), comparator); - ArrayPrototypeUnshift(sorted, output, output.length - keys.length, keys.length); - ArrayPrototypeSplice.$apply(null, sorted); + } else { + const keysLength = keys.length; + if (keysLength > 1) { + const sorted = ArrayPrototypeSort(ArrayPrototypeSlice(output, output.length - keysLength), comparator); + ArrayPrototypeUnshift(sorted, output, output.length - keysLength, keysLength); + ArrayPrototypeSplice.$apply(null, sorted); + } } } @@ -1831,8 +1842,9 @@ function formatError(err, constructor, tag, ctx, keys) { stack = newStack; } // The message and the stack have to be indented as well! - if (ctx.indentationLvl !== 0) { - const indentation = StringPrototypeRepeat(" ", ctx.indentationLvl); + const indentationLvl = ctx.indentationLvl; + if (indentationLvl !== 0) { + const indentation = StringPrototypeRepeat(" ", indentationLvl); stack = StringPrototypeReplaceAll(stack, "\n", `\n${indentation}`); } return stack; @@ -1865,7 +1877,9 @@ function groupArrayElements(ctx, output, value) { // of arrays that contains entries of very different length (i.e., if a single // entry is longer than 1/5 of all other entries combined). Otherwise the // space in-between small entries would be enormous. - if (actualMax * 3 + ctx.indentationLvl < ctx.breakLength && (totalLength / actualMax > 5 || maxLength <= 6)) { + const indentationLvl = ctx.indentationLvl; + const breakLength = ctx.breakLength; + if (actualMax * 3 + indentationLvl < breakLength && (totalLength / actualMax > 5 || maxLength <= 6)) { const approxCharHeights = 2.5; const averageBias = MathSqrt(actualMax - totalLength / output.length); const biasedMax = MathMax(actualMax - 3 - averageBias, 1); @@ -1878,7 +1892,7 @@ function groupArrayElements(ctx, output, value) { // The added bias increases the columns for short entries. MathRound(MathSqrt(approxCharHeights * biasedMax * outputLength) / biasedMax), // Do not exceed the breakLength. - MathFloor((ctx.breakLength - ctx.indentationLvl) / actualMax), + MathFloor((breakLength - indentationLvl) / actualMax), // Limit array grouping for small `compact` modes as the user requested // minimal grouping. ctx.compact * 4, @@ -1994,23 +2008,27 @@ function formatBigInt(fn, bigint, numericSeparator) { function formatPrimitive(fn, value, ctx) { if (typeof value === "string") { let trailer = ""; - if (value.length > ctx.maxStringLength) { - const remaining = value.length - ctx.maxStringLength; - value = StringPrototypeSlice(value, 0, ctx.maxStringLength); + const maxStringLength = ctx.maxStringLength; + let valueLength = value.length; + if (valueLength > maxStringLength) { + const remaining = valueLength - maxStringLength; + value = StringPrototypeSlice(value, 0, maxStringLength); + valueLength = value.length; trailer = `... ${remaining} more character${remaining > 1 ? "s" : ""}`; } + const indentationLvl = ctx.indentationLvl; if ( ctx.compact !== true && // We do not support handling unicode characters width with // the readline getStringWidth function as there are // performance implications. - value.length > kMinLineLength && - value.length > ctx.breakLength - ctx.indentationLvl - 4 + valueLength > kMinLineLength && + valueLength > ctx.breakLength - indentationLvl - 4 ) { return ( ArrayPrototypeJoin( ArrayPrototypeMap(extractedSplitNewLines(value), line => fn(strEscape(line), "string")), - ` +\n${StringPrototypeRepeat(" ", ctx.indentationLvl + 2)}`, + ` +\n${StringPrototypeRepeat(" ", indentationLvl + 2)}`, ) + trailer ); } @@ -2297,15 +2315,17 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc, original = va let name, str; let extra = " "; desc ||= ObjectGetOwnPropertyDescriptor(value, key) || { value: value[key], enumerable: true }; - if (desc.value !== undefined) { + const descValue = desc.value; + let descGet; + if (descValue !== undefined) { const diff = ctx.compact !== true || type !== kObjectType ? 2 : 3; ctx.indentationLvl += diff; - str = formatValue(ctx, desc.value, recurseTimes); + str = formatValue(ctx, descValue, recurseTimes); if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) { extra = `\n${StringPrototypeRepeat(" ", ctx.indentationLvl)}`; } ctx.indentationLvl -= diff; - } else if (desc.get !== undefined) { + } else if ((descGet = desc.get) !== undefined) { const label = desc.set !== undefined ? "Getter/Setter" : "Getter"; const s = ctx.stylize; const sp = "special"; @@ -2316,7 +2336,7 @@ function formatProperty(ctx, value, recurseTimes, key, type, desc, original = va (ctx.getters === "set" && desc.set !== undefined)) ) { try { - const tmp = desc.get.$call(original); + const tmp = descGet.$call(original); ctx.indentationLvl += 2; if (tmp === null) { str = `${s(`[${label}:`, sp)} ${s("null", "null")}${s("]", sp)}`; @@ -2379,8 +2399,9 @@ function isBelowBreakLength(ctx, output, start, base) { } function reduceToSingleString(ctx, output, base, braces, extrasType, recurseTimes, value) { - if (ctx.compact !== true) { - if (typeof ctx.compact === "number" && ctx.compact >= 1) { + const compact = ctx.compact; + if (compact !== true) { + if (typeof compact === "number" && compact >= 1) { // Memorize the original output length. In case the output is grouped, // prevent lining up the entries on a single line. const entries = output.length; @@ -2403,11 +2424,12 @@ function reduceToSingleString(ctx, output, base, braces, extrasType, recurseTime // Consolidate all entries of the local most inner depth up to // `ctx.compact`, as long as the properties are smaller than // `ctx.breakLength`. - if (ctx.currentDepth - recurseTimes < ctx.compact && entries === output.length) { + let outputLength; + if (ctx.currentDepth - recurseTimes < compact && entries === (outputLength = output.length)) { // Line up all entries on a single line in case the entries do not // exceed `breakLength`. Add 10 as constant to start next to all other // factors that may reduce `breakLength`. - const start = output.length + ctx.indentationLvl + braces[0].length + base.length + 10; + const start = outputLength + ctx.indentationLvl + braces[0].length + base.length + 10; if (isBelowBreakLength(ctx, output, start, base)) { const joinedOutput = ArrayPrototypeJoin(output, ", "); if (!StringPrototypeIncludes(joinedOutput, "\n")) { @@ -2722,7 +2744,8 @@ function previewEntries(val, isIterator = false) { } function internalGetConstructorName(val) { if (!val || typeof val !== "object") throw new Error("Invalid object"); - if (val.constructor?.name) return val.constructor.name; + const ctorName = val.constructor?.name; + if (ctorName) return ctorName; const str = ObjectPrototypeToString(val); const m = StringPrototypeMatch(str, /^\[object ([^\]]+)\]/); // e.g. [object Boolean] return m ? m[1] : "Object"; diff --git a/src/js/internal/webstreams_adapters.ts b/src/js/internal/webstreams_adapters.ts index e3bae79fdb7..002c257ff0e 100644 --- a/src/js/internal/webstreams_adapters.ts +++ b/src/js/internal/webstreams_adapters.ts @@ -293,8 +293,11 @@ function newWritableStreamFromStreamWritable(streamWritable, options = kEmptyObj if (!streamWritable.writableObjectMode && isAnyArrayBuffer(chunk)) { chunk = new Uint8Array(chunk); } - if (streamWritable.writableNeedDrain || !streamWritable.write(chunk)) { + const needDrainBefore = streamWritable.writableNeedDrain; + if (needDrainBefore || !streamWritable.write(chunk)) { backpressurePromise = PromiseWithResolvers(); + // write() may set writableNeedDrain; the post-write value is + // what decides whether we resolve immediately. if (!streamWritable.writableNeedDrain) { backpressurePromise.resolve(); } @@ -493,11 +496,12 @@ function newReadableStreamFromStreamReadable(streamReadable, options = kEmptyObj throw $ERR_INVALID_ARG_TYPE("streamReadable", "stream.Readable", streamReadable); } validateObject(options, "options"); - if (options.type !== undefined) { - validateOneOf(options.type, "options.type", ["bytes", undefined]); + const optionsType = options.type; + if (optionsType !== undefined) { + validateOneOf(optionsType, "options.type", ["bytes", undefined]); } - const isBYOB = options.type === "bytes"; + const isBYOB = optionsType === "bytes"; let controller; let wasCanceled = false; let strategy; @@ -635,10 +639,11 @@ function newReadableWritablePairFromDuplex(duplex, options = kEmptyObject) { type: options.readableType, }; - if (options.readableType == null && options.type != null) { + let optionsType; + if (options.readableType == null && (optionsType = options.type) != null) { // 'options.type' is a deprecated alias for 'options.readableType' emitDEP0201(); - readableOptions.type = options.type; + readableOptions.type = optionsType; } if (isDestroyed(duplex)) { diff --git a/src/js/node/_http2_upgrade.ts b/src/js/node/_http2_upgrade.ts index daa028b4322..74a1c522ece 100644 --- a/src/js/node/_http2_upgrade.ts +++ b/src/js/node/_http2_upgrade.ts @@ -216,17 +216,19 @@ function socketHandshake( tlsSocket.alpnProtocol = nativeHandle?.alpnProtocol ?? null; // Handle mutual TLS: if the server requested a client cert, check for errors - if (tlsSocket._requestCert || tlsSocket._rejectUnauthorized) { + const requestCert = tlsSocket._requestCert; + let rejectUnauthorized; + if (requestCert || (rejectUnauthorized = tlsSocket._rejectUnauthorized)) { if (verifyError) { tlsSocket.authorized = false; tlsSocket.authorizationError = verifyError.code || verifyError.message; ctx.server.emit("tlsClientError", verifyError, tlsSocket); - if (tlsSocket._rejectUnauthorized) { + if (rejectUnauthorized ?? tlsSocket._rejectUnauthorized) { tlsSocket.emit("secure", tlsSocket); tlsSocket.destroy(verifyError); return; } - } else if (tlsSocket._requestCert) { + } else if (requestCert) { tlsSocket.authorized = true; } } diff --git a/src/js/node/_http_agent.ts b/src/js/node/_http_agent.ts index 38969fa2803..bd73ba7e652 100644 --- a/src/js/node/_http_agent.ts +++ b/src/js/node/_http_agent.ts @@ -59,8 +59,9 @@ function Agent(options): void { validateOneOf(this.scheduling, "scheduling", ["fifo", "lifo"]); - if (this.maxTotalSockets !== undefined) { - validateNumber(this.maxTotalSockets, "maxTotalSockets", 1); + const maxTotalSockets = this.maxTotalSockets; + if (maxTotalSockets !== undefined) { + validateNumber(maxTotalSockets, "maxTotalSockets", 1); } else { this.maxTotalSockets = Infinity; } @@ -103,7 +104,8 @@ function Agent(options): void { const freeSockets = this.freeSockets[name] || []; const freeLen = freeSockets.length; let count = freeLen; - if (this.sockets[name]) count += this.sockets[name].length; + const namedSockets = this.sockets[name]; + if (namedSockets) count += namedSockets.length; if ( this.totalSocketCount > this.maxTotalSockets || @@ -178,18 +180,19 @@ Agent.prototype.createConnection = function createConnection(...args) { }; Agent.prototype.getName = function getName(options = kEmptyObject) { - let name = options.host || "localhost"; + const { host, port, localAddress, family, socketPath } = options; + let name = host || "localhost"; name += ":"; - if (options.port) name += options.port; + if (port) name += port; name += ":"; - if (options.localAddress) name += options.localAddress; + if (localAddress) name += localAddress; // Pacify parallel/test-http-agent-getname by only appending the ':' when options.family is set. - if (options.family === 4 || options.family === 6) name += `:${options.family}`; + if (family === 4 || family === 6) name += `:${family}`; - if (options.socketPath) name += `:${options.socketPath}`; + if (socketPath) name += `:${socketPath}`; return name; }; @@ -216,7 +219,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port /* legacy */ // Here the agent options will override per-request options. options = { __proto__: null, ...options, ...this.options }; - if (options.socketPath) options.path = options.socketPath; + const socketPath = options.socketPath; + if (socketPath) options.path = socketPath; normalizeServerName(options, req); @@ -261,7 +265,8 @@ Agent.prototype.addRequest = function addRequest(req, options, port /* legacy */ Agent.prototype.createSocket = function createSocket(req, options, cb) { options = { __proto__: null, ...options, ...this.options }; - if (options.socketPath) options.path = options.socketPath; + const socketPath = options.socketPath; + if (socketPath) options.path = socketPath; normalizeServerName(options, req); @@ -290,8 +295,9 @@ Agent.prototype.createSocket = function createSocket(req, options, cb) { installListeners(this, s, options); cb(null, s); }); - if (this.keepAlive) { - options.keepAlive = this.keepAlive; + const keepAlive = this.keepAlive; + if (keepAlive) { + options.keepAlive = keepAlive; options.keepAliveInitialDelay = this.keepAliveMsecs; } @@ -394,11 +400,12 @@ Agent.prototype.removeSocket = function removeSocket(s, options) { } let req; - if (this.requests[name]?.length) { + const requests = this.requests; + if (requests[name]?.length) { $debug("removeSocket, have a request, make a socket"); - req = this.requests[name][0]; + req = requests[name][0]; } else { - const keys = Object.keys(this.requests); + const keys = Object.keys(requests); for (let i = 0; i < keys.length; i++) { const prop = keys[i]; if (this.sockets[prop]?.length) break; diff --git a/src/js/node/_http_client.ts b/src/js/node/_http_client.ts index 53175c7b84c..70acba2503a 100644 --- a/src/js/node/_http_client.ts +++ b/src/js/node/_http_client.ts @@ -121,8 +121,9 @@ function rewriteForProxiedHttp(req, reqOptions) { const requestHost = req.getHeader("host") || "localhost"; const requestBase = `http://${requestHost}`; const requestURL = new URL(req.path, requestBase); - if (reqOptions.port) { - requestURL.port = reqOptions.port; + const reqOptionsPort = reqOptions.port; + if (reqOptionsPort) { + requestURL.port = reqOptionsPort; } req.path = requestURL.href; return true; @@ -170,10 +171,12 @@ function ClientRequest(input, options, cb) { const protocol = options.protocol || defaultAgent.protocol; let expectedProtocol = defaultAgent.protocol; - if (this.agent?.protocol) expectedProtocol = this.agent.protocol; + const agentProtocol = this.agent?.protocol; + if (agentProtocol) expectedProtocol = agentProtocol; - if (options.path) { - const path = String(options.path); + const optionsPath = options.path; + if (optionsPath) { + const path = String(optionsPath); if (INVALID_PATH_REGEX.test(path)) { throw $ERR_UNESCAPED_CHARACTERS("Request path"); } @@ -199,7 +202,8 @@ function ClientRequest(input, options, cb) { this.socketPath = options.socketPath; - if (options.timeout !== undefined) this.timeout = getTimerDuration(options.timeout, "timeout"); + const optionsTimeout = options.timeout; + if (optionsTimeout !== undefined) this.timeout = getTimerDuration(optionsTimeout, "timeout"); const signal = options.signal; if (signal) { @@ -231,11 +235,12 @@ function ClientRequest(input, options, cb) { this.insecureHTTPParser = insecureHTTPParser; - if (options.joinDuplicateHeaders !== undefined) { - validateBoolean(options.joinDuplicateHeaders, "options.joinDuplicateHeaders"); + const joinDuplicateHeaders = options.joinDuplicateHeaders; + if (joinDuplicateHeaders !== undefined) { + validateBoolean(joinDuplicateHeaders, "options.joinDuplicateHeaders"); } - this.joinDuplicateHeaders = options.joinDuplicateHeaders; + this.joinDuplicateHeaders = joinDuplicateHeaders; this[kPath] = options.path || "/"; if (cb) { @@ -266,12 +271,13 @@ function ClientRequest(input, options, cb) { this.host = host; this.protocol = protocol; - if (this.agent) { + const thisAgent = this.agent; + if (thisAgent) { // If there is an agent we should default to Connection:keep-alive, // but only if the Agent will actually reuse the connection! // If it's not a keepAlive agent, and the maxSockets==Infinity, then // there's never a case where this socket will actually be reused - if (!this.agent.keepAlive && !NumberIsFinite(this.agent.maxSockets)) { + if (!thisAgent.keepAlive && !NumberIsFinite(thisAgent.maxSockets)) { this._last = true; this.shouldKeepAlive = false; } else { @@ -280,15 +286,16 @@ function ClientRequest(input, options, cb) { } } - const headersArray = ArrayIsArray(options.headers); + const optionsHeaders = options.headers; + const headersArray = ArrayIsArray(optionsHeaders); if (!headersArray) { - if (options.headers) { - const keys = ObjectKeys(options.headers); + if (optionsHeaders) { + const keys = ObjectKeys(optionsHeaders); // Retain for(;;) loop for performance reasons // Refs: https://github.com/nodejs/node/pull/30958 for (let i = 0; i < keys.length; i++) { const key = keys[i]; - this.setHeader(key, options.headers[key]); + this.setHeader(key, optionsHeaders[key]); } } @@ -309,8 +316,9 @@ function ClientRequest(input, options, cb) { this.setHeader("Host", hostHeader); } - if (options.auth && !this.getHeader("Authorization")) { - this.setHeader("Authorization", "Basic " + Buffer.from(options.auth).toString("base64")); + const auth = options.auth; + if (auth && !this.getHeader("Authorization")) { + this.setHeader("Authorization", "Basic " + Buffer.from(auth).toString("base64")); } if (this.getHeader("expect")) { @@ -325,23 +333,25 @@ function ClientRequest(input, options, cb) { } } else { rewriteForProxiedHttp(this, optsWithoutSignal); - this._storeHeader(this.method + " " + this.path + " HTTP/1.1\r\n", options.headers); + this._storeHeader(this.method + " " + this.path + " HTTP/1.1\r\n", optionsHeaders); } this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders); // initiate connection - if (this.agent) { - this.agent.addRequest(this, optsWithoutSignal); + if (thisAgent) { + thisAgent.addRequest(this, optsWithoutSignal); } else { // No agent, default to Connection:close. this._last = true; this.shouldKeepAlive = false; let opts = optsWithoutSignal; - if (opts.path || opts.socketPath) { + let socketPath; + if (opts.path || (socketPath = opts.socketPath)) { opts = { ...optsWithoutSignal }; - if (opts.socketPath) { - opts.path = opts.socketPath; + socketPath ??= opts.socketPath; + if (socketPath) { + opts.path = socketPath; } else { opts.path &&= undefined; } @@ -431,8 +441,9 @@ ClientRequest.prototype.destroy = function destroy(err) { this.destroyed = true; // If we're aborting, we don't care about any more response data. - if (this.res) { - this.res._dump(); + const res = this.res; + if (res) { + res._dump(); } this[kError] = err; @@ -489,7 +500,8 @@ function socketCloseListener() { // Too bad. That output wasn't getting written. // This is pretty terrible that it doesn't raise an error. // Fixed better in v0.10 - if (req.outputData) req.outputData.length = 0; + const outputData = req.outputData; + if (outputData) outputData.length = 0; if (parser) { parser.finish(); @@ -556,57 +568,60 @@ function socketOnData(d) { socket.destroy(); req.socket._hadError = true; emitErrorEvent(req, ret); - } else if (parser.incoming?.upgrade) { - // Upgrade (if status code 101) or CONNECT - const bytesParsed = ret; + } else { const res = parser.incoming; - req.res = res; + if (res?.upgrade) { + // Upgrade (if status code 101) or CONNECT + const bytesParsed = ret; + req.res = res; - socket.removeListener("data", socketOnData); - socket.removeListener("end", socketOnEnd); - socket.removeListener("drain", ondrain); + socket.removeListener("data", socketOnData); + socket.removeListener("end", socketOnEnd); + socket.removeListener("drain", ondrain); - if (req.timeoutCb) socket.removeListener("timeout", req.timeoutCb); - socket.removeListener("timeout", responseOnTimeout); + const timeoutCb = req.timeoutCb; + if (timeoutCb) socket.removeListener("timeout", timeoutCb); + socket.removeListener("timeout", responseOnTimeout); - parser.finish(); - freeParser(parser, req, socket); + parser.finish(); + freeParser(parser, req, socket); - const bodyHead = d.slice(bytesParsed, d.length); + const bodyHead = d.slice(bytesParsed, d.length); - const eventName = req.method === "CONNECT" ? "connect" : "upgrade"; - if (req.listenerCount(eventName) > 0) { - req.upgradeOrConnect = true; + const eventName = req.method === "CONNECT" ? "connect" : "upgrade"; + if (req.listenerCount(eventName) > 0) { + req.upgradeOrConnect = true; - // detach the socket - socket.emit("agentRemove"); - socket.removeListener("close", socketCloseListener); - socket.removeListener("error", socketErrorListener); + // detach the socket + socket.emit("agentRemove"); + socket.removeListener("close", socketCloseListener); + socket.removeListener("error", socketErrorListener); - socket._httpMessage = null; - socket.readableFlowing = null; + socket._httpMessage = null; + socket.readableFlowing = null; - req.emit(eventName, res, socket, bodyHead); - req.destroyed = true; - req._closed = true; - req.emit("close"); - } else { - // Requested Upgrade or used CONNECT method, but have no handler. - socket.destroy(); + req.emit(eventName, res, socket, bodyHead); + req.destroyed = true; + req._closed = true; + req.emit("close"); + } else { + // Requested Upgrade or used CONNECT method, but have no handler. + socket.destroy(); + } + } else if ( + res?.complete && + // When the status code is informational (100, 102-199), + // the server will send a final response after this client + // sends a request body, so we must not free the parser. + // 101 (Switching Protocols) and all other status codes + // should be processed normally. + !statusIsInformational(res.statusCode) + ) { + socket.removeListener("data", socketOnData); + socket.removeListener("end", socketOnEnd); + socket.removeListener("drain", ondrain); + freeParser(parser, req, socket); } - } else if ( - parser.incoming?.complete && - // When the status code is informational (100, 102-199), - // the server will send a final response after this client - // sends a request body, so we must not free the parser. - // 101 (Switching Protocols) and all other status codes - // should be processed normally. - !statusIsInformational(parser.incoming.statusCode) - ) { - socket.removeListener("data", socketOnData); - socket.removeListener("end", socketOnEnd); - socket.removeListener("drain", ondrain); - freeParser(parser, req, socket); } } @@ -625,16 +640,18 @@ function parserOnIncomingClient(res, shouldKeepAlive) { $debug("AGENT incoming response!"); - if (req.res) { + const existingRes = req.res; + if (existingRes) { // We already have a response object, this means the server // sent a double response. socket.destroy(); - if (socket.parser) { + const parser = socket.parser; + if (parser) { // https://github.com/nodejs/node/issues/60025 // Now, parser.incoming is pointed to the new IncomingMessage, // we need to rewrite it to the first one and skip all the pending IncomingMessage - socket.parser.incoming = req.res; - socket.parser.incoming[kSkipPendingData] = true; + parser.incoming = existingRes; + parser.incoming[kSkipPendingData] = true; } return 0; } @@ -650,16 +667,17 @@ function parserOnIncomingClient(res, shouldKeepAlive) { return 2; // Skip body and treat as Upgrade. } - if (statusIsInformational(res.statusCode)) { + const statusCode = res.statusCode; + if (statusIsInformational(statusCode)) { // Restart the parser, as this is a 1xx informational message. req.res = null; // Clear res so that we don't hit double-responses. // Maintain compatibility by sending 100-specific events - if (res.statusCode === 100) { + if (statusCode === 100) { req.emit("continue"); } // Send information events to all 1xx responses except 101 Upgrade. req.emit("information", { - statusCode: res.statusCode, + statusCode, statusMessage: res.statusMessage, httpVersion: res.httpVersion, httpVersionMajor: res.httpVersionMajor, @@ -741,10 +759,11 @@ function responseKeepAlive(req) { process.nextTick(emitFreeNT, req); req.destroyed = true; - if (req.res) { + const reqRes = req.res; + if (reqRes) { // Detach socket from IncomingMessage to avoid destroying the freed // socket in IncomingMessage.destroy(). - req.res.socket = null; + reqRes.socket = null; } } @@ -803,8 +822,9 @@ function requestOnFinish() { function emitFreeNT(req) { req._closed = true; req.emit("close"); - if (req.socket) { - req.socket.emit("free"); + const socket = req.socket; + if (socket) { + socket.emit("free"); } } @@ -826,8 +846,9 @@ function tickOnSocket(req, socket) { socket._httpMessage = req; // Propagate headers limit from request object to parser - if (typeof req.maxHeadersCount === "number") { - parser.maxHeaderPairs = req.maxHeadersCount << 1; + const maxHeadersCount = req.maxHeadersCount; + if (typeof maxHeadersCount === "number") { + parser.maxHeaderPairs = maxHeadersCount << 1; } parser.joinDuplicateHeaders = req.joinDuplicateHeaders; @@ -862,8 +883,9 @@ function listenSocketTimeout(req) { // Set timeoutCb so it will get cleaned up on request end. req.timeoutCb = emitRequestTimeout; // Delegate socket timeout event. - if (req.socket) { - req.socket.once("timeout", emitRequestTimeout); + const reqSocket = req.socket; + if (reqSocket) { + reqSocket.once("timeout", emitRequestTimeout); } else { req.on("socket", onSocketListenTimeout); } @@ -959,8 +981,9 @@ ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) { msecs = getTimerDuration(msecs, "msecs"); if (callback) this.once("timeout", callback); - if (this.socket) { - setSocketTimeout(this.socket, msecs); + const socket = this.socket; + if (socket) { + setSocketTimeout(socket, msecs); } else { this.once("socket", onSocketSetTimeout.bind(undefined, msecs)); } diff --git a/src/js/node/_http_common.ts b/src/js/node/_http_common.ts index 709cd4e8400..fc69659c4b1 100644 --- a/src/js/node/_http_common.ts +++ b/src/js/node/_http_common.ts @@ -113,7 +113,8 @@ function parserOnHeadersComplete( let n = headers.length; // If parser.maxHeaderPairs <= 0 assume that there's no limit. - if (parser.maxHeaderPairs > 0) n = Math.min(n, parser.maxHeaderPairs); + const maxHeaderPairs = parser.maxHeaderPairs; + if (maxHeaderPairs > 0) n = Math.min(n, maxHeaderPairs); incoming._addHeaderLines(headers, n); @@ -150,8 +151,9 @@ function parserOnMessageComplete() { stream.complete = true; // Emit any trailing headers. const headers = parser._headers; - if (headers.length) { - stream._addHeaderLines(headers, headers.length); + const headersLength = headers.length; + if (headersLength) { + stream._addHeaderLines(headers, headersLength); parser._headers = []; parser._url = ""; } @@ -228,7 +230,8 @@ function cleanParser(parser) { function prepareError(err, parser, rawPacket) { err.rawPacket = rawPacket || parser.getCurrentBuffer(); - if (typeof err.reason === "string") err.message = `Parse Error: ${err.reason}`; + const reason = err.reason; + if (typeof reason === "string") err.message = `Parse Error: ${reason}`; } let warnedLenient = false; diff --git a/src/js/node/_http_outgoing.ts b/src/js/node/_http_outgoing.ts index 77c9b5b3640..811f224c1a3 100644 --- a/src/js/node/_http_outgoing.ts +++ b/src/js/node/_http_outgoing.ts @@ -286,13 +286,13 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteL // This is a shameful hack to get the headers and first body chunk onto // the same packet. Future versions of Node are going to take care of // this at a lower level and in a more general way. - if (!this._headerSent && this._header !== null) { + let header; + if (!this._headerSent && (header = this._header) !== null) { // `this._header` can be null if OutgoingMessage is used without a proper Socket // See: /test/parallel/test-http-outgoing-message-inheritance.js if (typeof data === "string" && (encoding === "utf8" || encoding === "latin1" || !encoding)) { - data = this._header + data; + data = header + data; } else { - const header = this._header; this.outputData.unshift({ data: header, encoding: "latin1", @@ -356,17 +356,18 @@ function _storeHeader(this: any, firstLine, headers) { processHeader(this, state, entry[0], entry[1], false); } } else if (ArrayIsArray(headers)) { - if (headers.length && ArrayIsArray(headers[0])) { - for (let i = 0; i < headers.length; i++) { + const headersLength = headers.length; + if (headersLength && ArrayIsArray(headers[0])) { + for (let i = 0; i < headersLength; i++) { const entry = headers[i]; processHeader(this, state, entry[0], entry[1], true); } } else { - if (headers.length % 2 !== 0) { + if (headersLength % 2 !== 0) { throw $ERR_INVALID_ARG_VALUE("headers", headers); } - for (let n = 0; n < headers.length; n += 2) { + for (let n = 0; n < headersLength; n += 2) { processHeader(this, state, headers[n + 0], headers[n + 1], true); } } @@ -414,11 +415,13 @@ function _storeHeader(this: any, firstLine, headers) { header += "Connection: close\r\n"; } else if (shouldSendKeepAlive) { header += "Connection: keep-alive\r\n"; - if (this._keepAliveTimeout && this._defaultKeepAlive) { - const timeoutSeconds = MathFloor(this._keepAliveTimeout / 1000); + const keepAliveTimeout = this._keepAliveTimeout; + if (keepAliveTimeout && this._defaultKeepAlive) { + const timeoutSeconds = MathFloor(keepAliveTimeout / 1000); let max = ""; - if (~~this._maxRequestsPerSocket > 0) { - max = `, max=${this._maxRequestsPerSocket}`; + const maxRequestsPerSocket = this._maxRequestsPerSocket; + if (~~maxRequestsPerSocket > 0) { + max = `, max=${maxRequestsPerSocket}`; } header += `Keep-Alive: timeout=${timeoutSeconds}${max}\r\n`; } @@ -434,18 +437,21 @@ function _storeHeader(this: any, firstLine, headers) { this.chunkedEncoding = false; } else if (!this.useChunkedEncodingByDefault) { this._last = true; - } else if (!state.trailer && !this._removedContLen && typeof this._contentLength === "number") { - header += "Content-Length: " + this._contentLength + "\r\n"; - } else if (!this._removedTE) { - header += "Transfer-Encoding: chunked\r\n"; - this.chunkedEncoding = true; } else { - // We should only be able to get here if both Content-Length and - // Transfer-Encoding are removed by the user. - // See: test/parallel/test-http-remove-header-stays-removed.js - // We can't keep alive in this case, because with no header info the body - // is defined as all data until the connection is closed. - this._last = true; + let contentLength; + if (!state.trailer && !this._removedContLen && typeof (contentLength = this._contentLength) === "number") { + header += "Content-Length: " + contentLength + "\r\n"; + } else if (!this._removedTE) { + header += "Transfer-Encoding: chunked\r\n"; + this.chunkedEncoding = true; + } else { + // We should only be able to get here if both Content-Length and + // Transfer-Encoding are removed by the user. + // See: test/parallel/test-http-remove-header-stays-removed.js + // We can't keep alive in this case, because with no header info the body + // is defined as all data until the connection is closed. + this._last = true; + } } } @@ -484,13 +490,14 @@ function processHeader(self, state, key, value, validate) { } if (ArrayIsArray(value)) { + const valueLength = value.length; if ( - (value.length < 2 || !isCookieField(key)) && + (valueLength < 2 || !isCookieField(key)) && (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(key.toLowerCase())) ) { // Retain for(;;) loop for performance reasons // Refs: https://github.com/nodejs/node/pull/30958 - for (let i = 0; i < value.length; i++) storeHeader(self, state, key, value[i], validate); + for (let i = 0; i < valueLength; i++) storeHeader(self, state, key, value[i], validate); return; } value = value.join("; "); @@ -807,11 +814,12 @@ function write_(msg, chunk, encoding, callback, fromEnd) { if (msg.strictContentLength) { len ??= typeof chunk === "string" ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; + const contentLength = msg._contentLength; if ( strictContentLength(msg) && - (fromEnd ? msg[kBytesWritten] + len !== msg._contentLength : msg[kBytesWritten] + len > msg._contentLength) + (fromEnd ? msg[kBytesWritten] + len !== contentLength : msg[kBytesWritten] + len > contentLength) ) { - throw $ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], msg._contentLength); + throw $ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], contentLength); } msg[kBytesWritten] += len; @@ -836,9 +844,10 @@ function write_(msg, chunk, encoding, callback, fromEnd) { } } - if (!fromEnd && msg.socket && !msg.socket.writableCorked) { - msg.socket.cork(); - process.nextTick(connectionCorkNT, msg.socket); + let msgSocket; + if (!fromEnd && (msgSocket = msg.socket) && !msgSocket.writableCorked) { + msgSocket.cork(); + process.nextTick(connectionCorkNT, msgSocket); } let ret; @@ -885,8 +894,9 @@ OutgoingMessage.prototype.addTrailers = function addTrailers(headers) { // Check if the field must be sent several times const isArrayValue = ArrayIsArray(value); - if (isArrayValue && value.length > 1 && (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(field.toLowerCase()))) { - for (let j = 0, l = value.length; j < l; j++) { + const valueLength = isArrayValue ? value.length : 0; + if (isArrayValue && valueLength > 1 && (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(field.toLowerCase()))) { + for (let j = 0, l = valueLength; j < l; j++) { if (checkInvalidHeaderChar(value[j])) { throw $ERR_INVALID_CHAR("trailer content", field); } @@ -951,8 +961,9 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { if (typeof callback === "function") this.once("finish", callback); - if (strictContentLength(this) && this[kBytesWritten] !== this._contentLength) { - throw $ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength); + let contentLength; + if (strictContentLength(this) && this[kBytesWritten] !== (contentLength = this._contentLength)) { + throw $ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], contentLength); } const finish = onFinish.bind(undefined, this); diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 8e515ee41c0..0cc07212035 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -438,24 +438,26 @@ Server.prototype.listen = function () { // This logic must align with: // - https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/net.js#L274-L307 - if (arguments.length > 0) { - if (($isObject(arguments[0]) || $isCallable(arguments[0])) && arguments[0] !== null) { + const argc = arguments.length; + if (argc > 0) { + const arg0 = arguments[0]; + if (($isObject(arg0) || $isCallable(arg0)) && arg0 !== null) { // (options[...][, cb]) - port = arguments[0].port; - host = arguments[0].host; - socketPath = arguments[0].path; + port = arg0.port; + host = arg0.host; + socketPath = arg0.path; - const otherTLS = arguments[0].tls; + const otherTLS = arg0.tls; if (otherTLS && $isObject(otherTLS)) { tls = normalizeServerTls({ ...otherTLS }); } - } else if (typeof arguments[0] === "string" && !(Number(arguments[0]) >= 0)) { + } else if (typeof arg0 === "string" && !(Number(arg0) >= 0)) { // (path[...][, cb]) - socketPath = arguments[0]; + socketPath = arg0; } else { // ([port][, host][...][, cb]) - port = arguments[0]; - if (arguments.length > 1 && typeof arguments[1] === "string") { + port = arg0; + if (argc > 1 && typeof arguments[1] === "string") { host = arguments[1]; } } @@ -474,8 +476,9 @@ Server.prototype.listen = function () { } } - if ($isCallable(arguments[arguments.length - 1])) { - onListen = arguments[arguments.length - 1]; + const lastArg = arguments[argc - 1]; + if ($isCallable(lastArg)) { + onListen = lastArg; } try { @@ -784,25 +787,28 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort // honor requireHostHeader, like Node.js. http_res.writeHead(400, { Connection: "close" }); http_res.end(); - } else if (http_req.headers.expect !== undefined) { - // Case-insensitive, token-boundary match like Node's - // parserOnIncoming (RFC 7231 5.1.1: expectation values compare - // case-insensitively). - if (continueExpression.test(http_req.headers.expect)) { - if (server.listenerCount("checkContinue") > 0) { - server.emit("checkContinue", http_req, http_res); + } else { + const expectHeader = http_req.headers.expect; + if (expectHeader !== undefined) { + // Case-insensitive, token-boundary match like Node's + // parserOnIncoming (RFC 7231 5.1.1: expectation values compare + // case-insensitively). + if (continueExpression.test(expectHeader)) { + if (server.listenerCount("checkContinue") > 0) { + server.emit("checkContinue", http_req, http_res); + } else { + http_res.writeContinue(); + server.emit("request", http_req, http_res); + } + } else if (server.listenerCount("checkExpectation") > 0) { + server.emit("checkExpectation", http_req, http_res); } else { - http_res.writeContinue(); - server.emit("request", http_req, http_res); + http_res.writeHead(417); + http_res.end(); } - } else if (server.listenerCount("checkExpectation") > 0) { - server.emit("checkExpectation", http_req, http_res); } else { - http_res.writeHead(417); - http_res.end(); + server.emit("request", http_req, http_res); } - } else { - server.emit("request", http_req, http_res); } socket.cork(); @@ -1520,8 +1526,9 @@ function renderNativeHeaders(res) { } } else if (key === "keep-alive") hasKeepAlive = true; if ($isArray(value)) { - if (value.length < 2 || key !== "cookie") { - for (let i = 0; i < value.length; i++) { + const valueLength = value.length; + if (valueLength < 2 || key !== "cookie") { + for (let i = 0; i < valueLength; i++) { flat.push(name, String(value[i])); } } else { @@ -1589,8 +1596,9 @@ function renderNativeHeaders(res) { const keepAliveTimeout = res._keepAliveTimeout; if (keepAliveTimeout && !hasKeepAlive) { let max = ""; - if (~~res._maxRequestsPerSocket > 0) { - max = `, max=${res._maxRequestsPerSocket}`; + const maxRequestsPerSocket = res._maxRequestsPerSocket; + if (~~maxRequestsPerSocket > 0) { + max = `, max=${maxRequestsPerSocket}`; } flat.push("Keep-Alive", `timeout=${MathFloor(keepAliveTimeout / 1000)}${max}`); } @@ -1879,16 +1887,17 @@ ServerResponse.prototype.writeInformation = function writeInformation(statusCode if (headers !== undefined && headers !== null) { if ($isArray(headers)) { - if (headers.length && $isArray(headers[0])) { - for (let i = 0; i < headers.length; i++) { + const headersLength = headers.length; + if (headersLength && $isArray(headers[0])) { + for (let i = 0; i < headersLength; i++) { const entry = headers[i]; head += processInformationHeader(entry[0], entry[1]); } } else { - if (headers.length % 2 !== 0) { + if (headersLength % 2 !== 0) { throw $ERR_INVALID_ARG_VALUE("headers", headers); } - for (let i = 0; i < headers.length; i += 2) { + for (let i = 0; i < headersLength; i += 2) { head += processInformationHeader(headers[i], headers[i + 1]); } } diff --git a/src/js/node/_tls_common.ts b/src/js/node/_tls_common.ts index bade6984757..ececbac7238 100644 --- a/src/js/node/_tls_common.ts +++ b/src/js/node/_tls_common.ts @@ -11,7 +11,7 @@ function translatePeerCertificate(c) { } if (c.infoAccess != null) { const info = c.infoAccess; - c.infoAccess = Object.create(null); + const parsed = (c.infoAccess = Object.create(null)); // XXX: More key validation? info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g, (all, key, val) => { @@ -23,8 +23,8 @@ function translatePeerCertificate(c) { // so this should never throw. val = JSON.parse(val); } - if (key in c.infoAccess) c.infoAccess[key].push(val); - else c.infoAccess[key] = [val]; + if (key in parsed) parsed[key].push(val); + else parsed[key] = [val]; }); } return c; diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index 7254dbde4f5..ec3b79b2d8d 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -97,7 +97,8 @@ const NO_EXCEPTION_SENTINEL = {}; // display purposes. function innerFail(obj) { - if (obj.message instanceof Error) throw obj.message; + const objMessage = obj.message; + if (objMessage instanceof Error) throw objMessage; throw new AssertionError(obj); } @@ -670,8 +671,9 @@ function expectedException(actual, expected, message, fn) { } else { message += `"${name}"`; } - if (actual.message) { - message += `\n\nError message:\n\n${actual.message}`; + const actualMessage = actual.message; + if (actualMessage) { + message += `\n\nError message:\n\n${actualMessage}`; } } else { message += `"${lazyInspect()(actual, { depth: -1 })}"`; @@ -890,11 +892,13 @@ assert.doesNotReject = async function doesNotReject(fn: () => Promise, assert.ifError = function ifError(err: unknown): void { if (err !== null && err !== undefined) { let message = "ifError got unwanted exception: "; - if (typeof err === "object" && typeof err.message === "string") { - if (err.message.length === 0 && err.constructor) { - message += err.constructor.name; + const errMessage = typeof err === "object" ? err.message : undefined; + if (typeof errMessage === "string") { + let errConstructor; + if (errMessage.length === 0 && (errConstructor = err.constructor)) { + message += errConstructor.name; } else { - message += err.message; + message += errMessage; } } else { const inspect = lazyInspect(); diff --git a/src/js/node/child_process.ts b/src/js/node/child_process.ts index ba167f5fe96..93f8ebf0d75 100644 --- a/src/js/node/child_process.ts +++ b/src/js/node/child_process.ts @@ -132,7 +132,8 @@ if ($debug) { */ function spawn(file, args, options) { options = normalizeSpawnArguments(file, args, options); - if (options.windowsBatchFileError) throw options.windowsBatchFileError; + const windowsBatchFileError = options.windowsBatchFileError; + if (windowsBatchFileError) throw windowsBatchFileError; validateTimeout(options.timeout); validateAbortSignal(options.signal, "options.signal"); const killSignal = sanitizeKillSignal(options.killSignal); @@ -246,8 +247,9 @@ function execFile(file, args, options, callback) { let encoding; const _stdout = []; const _stderr = []; - if (options.encoding !== "buffer" && BufferIsEncoding(options.encoding)) { - encoding = options.encoding; + const optionsEncoding = options.encoding; + if (optionsEncoding !== "buffer" && BufferIsEncoding(optionsEncoding)) { + encoding = optionsEncoding; } else { encoding = null; } @@ -332,11 +334,12 @@ function execFile(file, args, options, callback) { } } - if (options.timeout > 0) { + const optionsTimeout = options.timeout; + if (optionsTimeout > 0) { timeoutId = setTimeout(function delayedKill() { timeoutId = null; kill(); - }, options.timeout).unref(); + }, optionsTimeout).unref(); } function addOnDataListener(child_buffer, _buffer, kind) { @@ -381,8 +384,10 @@ function execFile(file, args, options, callback) { }); } - if (child.stdout) addOnDataListener(child.stdout, _stdout, "stdout"); - if (child.stderr) addOnDataListener(child.stderr, _stderr, "stderr"); + const childStdout = child.stdout; + if (childStdout) addOnDataListener(childStdout, _stdout, "stdout"); + const childStderr = child.stderr; + if (childStderr) addOnDataListener(childStderr, _stderr, "stderr"); child.addListener("close", exitHandler); child.addListener("error", errorHandler); @@ -621,9 +626,10 @@ function spawnSync(file, args, options) { ); } - if (result.error) { - result.error.syscall = "spawnSync " + options.file; - result.error.spawnargs = ArrayPrototypeSlice.$call(options.args, 1); + const resultError = result.error; + if (resultError) { + resultError.syscall = "spawnSync " + options.file; + resultError.spawnargs = ArrayPrototypeSlice.$call(options.args, 1); } return result; @@ -657,7 +663,8 @@ function execFileSync(file, args, options) { const inheritStderr = !options.stdio; const ret = spawnSync(file, args, options); - if (inheritStderr && ret.stderr) process.stderr.write(ret.stderr); + let retStderr; + if (inheritStderr && (retStderr = ret.stderr)) process.stderr.write(retStderr); const errArgs = [options.argv0 || file]; ArrayPrototypePush.$apply(errArgs, args); @@ -693,7 +700,8 @@ function execSync(command, options) { const ret = spawnSync(opts.file, opts.options); - if (inheritStderr && ret.stderr) process.stderr.write(ret.stderr); + let retStderr; + if (inheritStderr && (retStderr = ret.stderr)) process.stderr.write(retStderr); const err = checkExecSyncError(ret, undefined, command); @@ -770,8 +778,9 @@ function fork(modulePath, args = [], options) { let execArgv = options.execArgv || process.execArgv; validateArgumentsNullCheck(execArgv, "options.execArgv"); - if (execArgv === process.execArgv && process._eval != null) { - const index = ArrayPrototypeLastIndexOf.$call(execArgv, process._eval); + let processEval; + if (execArgv === process.execArgv && (processEval = process._eval) != null) { + const index = ArrayPrototypeLastIndexOf.$call(execArgv, processEval); if (index > 0) { // Remove the -e switch to avoid fork bombing ourselves. execArgv = ArrayPrototypeSlice.$call(execArgv); @@ -860,9 +869,10 @@ function normalizeExecFileArgs(file, args, options, callback) { } // Validate argv0, if present. - if (options.argv0 != null) { - validateString(options.argv0, "options.argv0"); - validateArgumentNullCheck(options.argv0, "options.argv0"); + const argv0 = options.argv0; + if (argv0 != null) { + validateString(argv0, "options.argv0"); + validateArgumentNullCheck(argv0, "options.argv0"); } return { file, args, options, callback }; @@ -920,34 +930,40 @@ function normalizeSpawnArguments(file, args, options) { } // Validate detached, if present. - if (options.detached != null) { - validateBoolean(options.detached, "options.detached"); + const detached = options.detached; + if (detached != null) { + validateBoolean(detached, "options.detached"); } // Validate the uid, if present. - if (options.uid != null && !isInt32(options.uid)) { - throw $ERR_INVALID_ARG_TYPE("options.uid", "int32", options.uid); + const uid = options.uid; + if (uid != null && !isInt32(uid)) { + throw $ERR_INVALID_ARG_TYPE("options.uid", "int32", uid); } // Validate the gid, if present. - if (options.gid != null && !isInt32(options.gid)) { - throw $ERR_INVALID_ARG_TYPE("options.gid", "int32", options.gid); + const gid = options.gid; + if (gid != null && !isInt32(gid)) { + throw $ERR_INVALID_ARG_TYPE("options.gid", "int32", gid); } // Validate the shell, if present. - if (options.shell != null && typeof options.shell !== "boolean" && typeof options.shell !== "string") { - throw $ERR_INVALID_ARG_TYPE("options.shell", ["boolean", "string"], options.shell); + const shell = options.shell; + if (shell != null && typeof shell !== "boolean" && typeof shell !== "string") { + throw $ERR_INVALID_ARG_TYPE("options.shell", ["boolean", "string"], shell); } // Validate argv0, if present. - if (options.argv0 != null) { - validateString(options.argv0, "options.argv0"); - validateArgumentNullCheck(options.argv0, "options.argv0"); + const argv0 = options.argv0; + if (argv0 != null) { + validateString(argv0, "options.argv0"); + validateArgumentNullCheck(argv0, "options.argv0"); } // Validate windowsHide, if present. - if (options.windowsHide != null) { - validateBoolean(options.windowsHide, "options.windowsHide"); + const windowsHide = options.windowsHide; + if (windowsHide != null) { + validateBoolean(windowsHide, "options.windowsHide"); } let { windowsVerbatimArguments } = options; @@ -957,12 +973,12 @@ function normalizeSpawnArguments(file, args, options) { let windowsBatchFileError: Error | undefined; // Handle shell - if (options.shell) { - validateArgumentNullCheck(options.shell, "options.shell"); + if (shell) { + validateArgumentNullCheck(shell, "options.shell"); const command = ArrayPrototypeJoin.$call([file, ...args], " "); // Set the shell, switches, and commands. if (process.platform === "win32") { - if (typeof options.shell === "string") file = options.shell; + if (typeof shell === "string") file = shell; else file = process.env.comspec || "cmd.exe"; // '/d /s /c' is used only for cmd.exe. if (/^(?:.*\\)?cmd(?:\.exe)?$/i.exec(file) !== null) { @@ -972,7 +988,7 @@ function normalizeSpawnArguments(file, args, options) { args = ["-c", command]; } } else { - if (typeof options.shell === "string") file = options.shell; + if (typeof shell === "string") file = shell; else if (process.platform === "android") file = "sh"; else file = "/bin/sh"; args = ["-c", command]; @@ -987,8 +1003,8 @@ function normalizeSpawnArguments(file, args, options) { } // Handle argv0 - if (typeof options.argv0 === "string") { - ArrayPrototypeUnshift.$call(args, options.argv0); + if (typeof argv0 === "string") { + ArrayPrototypeUnshift.$call(args, argv0); } else { ArrayPrototypeUnshift.$call(args, file); } @@ -1046,8 +1062,9 @@ function normalizeSpawnArguments(file, args, options) { function checkExecSyncError(ret, args, cmd?) { let err; - if (ret.error) { - err = ret.error; + const retError = ret.error; + if (retError) { + err = retError; ObjectAssign(err, ret); // ObjectAssign copies ret.error onto err, but err IS ret.error, // creating a self-referencing cycle (err.error === err). Remove it. @@ -1055,7 +1072,8 @@ function checkExecSyncError(ret, args, cmd?) { } else if (ret.status !== 0) { let msg = "Command failed: "; msg += cmd || ArrayPrototypeJoin.$call(args, " "); - if (ret.stderr && ret.stderr.length > 0) msg += `\n${ret.stderr.toString()}`; + const stderr = ret.stderr; + if (stderr && stderr.length > 0) msg += `\n${stderr.toString()}`; err = genericNodeError(msg, ret); } return err; @@ -1128,8 +1146,9 @@ class ChildProcess extends EventEmitter { } } + const spawnfile = this.spawnfile; if (err) { - if (this.spawnfile) err.path = this.spawnfile; + if (spawnfile) err.path = spawnfile; err.spawnargs = ArrayPrototypeSlice.$call(this.spawnargs, 1); err.pid = this.pid; this.emit("error", err); @@ -1143,7 +1162,7 @@ class ChildProcess extends EventEmitter { ); err.pid = this.pid; - if (this.spawnfile) err.path = this.spawnfile; + if (spawnfile) err.path = spawnfile; err.spawnargs = ArrayPrototypeSlice.$call(this.spawnargs, 1); this.emit("error", err); @@ -1331,14 +1350,15 @@ class ChildProcess extends EventEmitter { const has_ipc = $isJSArray(stdio) && stdio.includes("ipc"); + const envPairs = options.envPairs; // validate options.envPairs but only if has_ipc. for some reason. if (has_ipc) { - if (options.envPairs !== undefined) { - validateArray(options.envPairs, "options.envPairs"); + if (envPairs !== undefined) { + validateArray(envPairs, "options.envPairs"); } } - var env = options[kBunEnv] || parseEnvPairs(options.envPairs) || process.env; + var env = options[kBunEnv] || parseEnvPairs(envPairs) || process.env; const detachedOption = options.detached; this.#encoding = options.encoding || undefined; @@ -1425,16 +1445,14 @@ class ChildProcess extends EventEmitter { } } } catch (ex) { + const exCode = ex != null && typeof ex === "object" && Object.hasOwn(ex, "code") ? ex.code : undefined; if ( - ex != null && - typeof ex === "object" && - Object.hasOwn(ex, "code") && // node sends these errors on the next tick rather than throwing - (ex.code === "EACCES" || - ex.code === "EAGAIN" || - ex.code === "EMFILE" || - ex.code === "ENFILE" || - ex.code === "ENOENT") + exCode === "EACCES" || + exCode === "EAGAIN" || + exCode === "EMFILE" || + exCode === "ENFILE" || + exCode === "ENOENT" ) { this.#handle = null; ex.syscall = "spawn " + this.spawnfile; @@ -1443,7 +1461,7 @@ class ChildProcess extends EventEmitter { this.emit("error", ex); this.emit("close", (ex as SystemError).errno ?? -1); }); - if (ex.code === "EMFILE" || ex.code === "ENFILE") { + if (exCode === "EMFILE" || exCode === "ENFILE") { // emfile/enfile error; in this case node does not initialize stdio streams. this.#stdioOptions[0] = "undefined"; this.#stdioOptions[1] = "undefined"; @@ -1641,13 +1659,19 @@ function nodeToBun(item: string, index: number): string | number | null | NodeJS return item; } if (isNodeStreamReadable(item)) { - if (Object.hasOwn(item, "fd") && typeof item.fd === "number") return item.fd; - if (item._handle && typeof item._handle.fd === "number") return item._handle.fd; + const itemFd = Object.hasOwn(item, "fd") ? item.fd : undefined; + if (typeof itemFd === "number") return itemFd; + const handle = item._handle; + const handleFd = handle ? handle.fd : undefined; + if (typeof handleFd === "number") return handleFd; throw new Error(`TODO: stream.Readable stdio @ ${index}`); } if (isNodeStreamWritable(item)) { - if (Object.hasOwn(item, "fd") && typeof item.fd === "number") return item.fd; - if (item._handle && typeof item._handle.fd === "number") return item._handle.fd; + const itemFd = Object.hasOwn(item, "fd") ? item.fd : undefined; + if (typeof itemFd === "number") return itemFd; + const handle = item._handle; + const handleFd = handle ? handle.fd : undefined; + if (typeof handleFd === "number") return handleFd; throw new Error(`TODO: stream.Writable stdio @ ${index}`); } const result = nodeToBunLookup[item]; diff --git a/src/js/node/dns.ts b/src/js/node/dns.ts index 27adcf40d34..dd3f00d6ba2 100644 --- a/src/js/node/dns.ts +++ b/src/js/node/dns.ts @@ -147,10 +147,11 @@ function validateFlagsOption(options) { return; } - validateNumber(options.flags); + const flags = options.flags; + validateNumber(flags); - if ((options.flags & ~(dns.ALL | dns.ADDRCONFIG | dns.V4MAPPED)) != 0) { - throw $ERR_INVALID_ARG_VALUE("hints", options.flags, "is invalid"); + if ((flags & ~(dns.ALL | dns.ADDRCONFIG | dns.V4MAPPED)) != 0) { + throw $ERR_INVALID_ARG_VALUE("hints", flags, "is invalid"); } } @@ -177,14 +178,16 @@ function validateFamilyOption(options) { } function validateAllOption(options) { - if (options.all !== undefined) { - validateBoolean(options.all); + const all = options.all; + if (all !== undefined) { + validateBoolean(all); } } function validateVerbatimOption(options) { - if (options.verbatim !== undefined) { - validateBoolean(options.verbatim); + const verbatim = options.verbatim; + if (verbatim !== undefined) { + validateBoolean(verbatim); } } @@ -195,8 +198,9 @@ function validateOrder(order) { } function validateOrderOption(options) { - if (options.order !== undefined) { - validateOrder(options.order); + const order = options.order; + if (order !== undefined) { + validateOrder(order); } } @@ -324,9 +328,10 @@ function lookup(hostname, options, callback) { if (err.code?.startsWith("DNS_")) err.code = err.code.slice(4); // Node.js getaddrinfo errors (DNSException) carry the looked-up // hostname both as a property and at the end of the message. - if (err.syscall === "getaddrinfo" && !err.hostname && hostname) { + const syscall = err.syscall; + if (syscall === "getaddrinfo" && !err.hostname && hostname) { err.hostname = hostname; - err.message = `${err.syscall} ${err.code} ${hostname}`; + err.message = `${syscall} ${err.code} ${hostname}`; } callback(err, undefined, undefined); }); diff --git a/src/js/node/fs.promises.ts b/src/js/node/fs.promises.ts index 95c4d13c651..7e45225c1fe 100644 --- a/src/js/node/fs.promises.ts +++ b/src/js/node/fs.promises.ts @@ -176,18 +176,11 @@ async function cp(src, dest, options) { options = validateCpOptions(options); src = getValidatedFsPath(src, "src"); dest = getValidatedFsPath(dest, "dest"); - if ( - !options.filter && - !options.dereference && - !options.preserveTimestamps && - !options.verbatimSymlinks && - !options.mode && - !options.errorOnExist && - options.force - ) { + const { filter, dereference, preserveTimestamps, verbatimSymlinks, mode, errorOnExist, force, recursive } = options; + if (!filter && !dereference && !preserveTimestamps && !verbatimSymlinks && !mode && !errorOnExist && force) { const { ok, checked } = await require("internal/fs/cp").tryNativeFastPath(src, dest, options); if (ok) { - return fs.cp(src, dest, options.recursive, options.errorOnExist, options.force, options.mode); + return fs.cp(src, dest, recursive, errorOnExist, force, mode); } return require("internal/fs/cp").cpFn(src, dest, options, checked); } @@ -1146,14 +1139,16 @@ function asyncWrap(fn: any, name: string) { } } chunk = toUint8Array(chunk); - if (bytesRemaining >= 0 && chunk.byteLength > bytesRemaining) { - return Promise.$reject($ERR_OUT_OF_RANGE("write", `<= ${bytesRemaining} bytes`, chunk.byteLength)); + let chunkByteLength; + if (bytesRemaining >= 0 && (chunkByteLength = chunk.byteLength) > bytesRemaining) { + return Promise.$reject($ERR_OUT_OF_RANGE("write", `<= ${bytesRemaining} bytes`, chunkByteLength)); } - if (bytesRemaining > 0) bytesRemaining -= chunk.byteLength; + if (bytesRemaining > 0) bytesRemaining -= chunkByteLength; + chunkByteLength ??= chunk.byteLength; const position = pos; - if (pos >= 0) pos += chunk.byteLength; + if (pos >= 0) pos += chunkByteLength; acquireRef(); - return writeAll(chunk, 0, chunk.byteLength, position, signal); + return writeAll(chunk, 0, chunkByteLength, position, signal); }, writev(chunks, options = kEmptyObject) { diff --git a/src/js/node/fs.ts b/src/js/node/fs.ts index e3d8bfbd06a..bcbd183d398 100644 --- a/src/js/node/fs.ts +++ b/src/js/node/fs.ts @@ -231,8 +231,9 @@ var access = function access(path, mode, callback) { // fd = getValidatedFd(fd); DEFERRED TO NATIVE let offset = offsetOrOptions; let params: any = null; - if (arguments.length <= 4) { - if (arguments.length === 4) { + const argc = arguments.length; + if (argc <= 4) { + if (argc === 4) { // This is fs.read(fd, buffer, options, callback) // validateObject(params, 'options', kValidateObjectAllowNullable); if (typeof params !== "object" || $isArray(params)) { @@ -240,7 +241,7 @@ var access = function access(path, mode, callback) { } callback = length; params = offsetOrOptions; - } else if (arguments.length === 3) { + } else if (argc === 3) { // This is fs.read(fd, bufferOrParams, callback) if (!types.isArrayBufferView(buffer)) { // fs.read(fd, bufferOrParams, callback) @@ -672,8 +673,9 @@ const realpathSync: typeof import("node:fs").realpathSync = // resolve subst drives to their underlying location. The native call is // able to see through that. if (p instanceof URL) { - if (p.pathname.indexOf("%00") != -1) { - throw $ERR_INVALID_ARG_VALUE("path", "string without null bytes", p.pathname); + const pathname = p.pathname; + if (pathname.indexOf("%00") != -1) { + throw $ERR_INVALID_ARG_VALUE("path", "string without null bytes", pathname); } p = Bun.fileURLToPath(p as URL); } else { @@ -791,8 +793,9 @@ const realpath: typeof import("node:fs").realpath = } } if (p instanceof URL) { - if (p.pathname.indexOf("%00") != -1) { - throw $ERR_INVALID_ARG_VALUE("path", "string without null bytes", p.pathname); + const pathname = p.pathname; + if (pathname.indexOf("%00") != -1) { + throw $ERR_INVALID_ARG_VALUE("path", "string without null bytes", pathname); } p = Bun.fileURLToPath(p as URL); } else { @@ -935,18 +938,11 @@ function cpSync(src, dest, options) { options = validateCpOptions(options); src = getValidatedFsPath(src, "src"); dest = getValidatedFsPath(dest, "dest"); - if ( - !options.filter && - !options.dereference && - !options.preserveTimestamps && - !options.verbatimSymlinks && - !options.mode && - !options.errorOnExist && - options.force - ) { + const { filter, dereference, preserveTimestamps, verbatimSymlinks, mode, errorOnExist, force, recursive } = options; + if (!filter && !dereference && !preserveTimestamps && !verbatimSymlinks && !mode && !errorOnExist && force) { const { ok, checked } = tryNativeFastPathSync(src, dest, options); if (ok) { - return fs.cpSync(src, dest, options.recursive, options.errorOnExist, options.force, options.mode); + return fs.cpSync(src, dest, recursive, errorOnExist, force, mode); } return cpSyncFn(src, dest, options, checked); } diff --git a/src/js/node/https.ts b/src/js/node/https.ts index 80efdb69ab2..1d1104717b8 100644 --- a/src/js/node/https.ts +++ b/src/js/node/https.ts @@ -188,11 +188,12 @@ function establishTunnel(agent, socket, options, tunnelConfig, afterSocket) { tunneledSocket = tls.connect(requestOptions, onTLSHandshakeSuccess); tunneledSocket.on("free", onTunneledSocketFree); tunneledSocket.on("error", onTLSHandshakeError); - if (requestOptions._agentKey) { + const agentKey = requestOptions._agentKey; + if (agentKey) { // The tunneled socket carries the TLS session with the target; cache // it (and evict on close) under the target's agent key. - tunneledSocket.on("session", onSocketSession.bind(agent, requestOptions._agentKey)); - tunneledSocket.once("close", onSocketClose.bind(agent, requestOptions._agentKey)); + tunneledSocket.on("session", onSocketSession.bind(agent, agentKey)); + tunneledSocket.once("close", onSocketClose.bind(agent, agentKey)); } } return headerEndIndex; @@ -249,16 +250,18 @@ function createConnection(...args) { options.host = args[1]; } let cb; - if (typeof args[args.length - 1] === "function") { - cb = args[args.length - 1]; + const lastArg = args[args.length - 1]; + if (typeof lastArg === "function") { + cb = lastArg; } $debug("https createConnection", options); - if (options._agentKey) { - const session = this._getSession(options._agentKey); + const agentKey = options._agentKey; + if (agentKey) { + const session = this._getSession(agentKey); if (session) { - $debug("reuse session for %j", options._agentKey); + $debug("reuse session for %j", agentKey); options = { session, ...options, @@ -322,15 +325,15 @@ function createConnection(...args) { socket[kWaitForProxyTunnel] = true; } - if (options._agentKey && tunnelConfig === null) { + if (agentKey && tunnelConfig === null) { // Cache new session for reuse. On the proxy-tunnel path `socket` is the // connection to the proxy, not the target - establishTunnel attaches // these listeners to the tunneled target socket instead, so the proxy's // session is never cached under the target's key. - socket.on("session", onSocketSession.bind(this, options._agentKey)); + socket.on("session", onSocketSession.bind(this, agentKey)); // Evict session on error - socket.once("close", onSocketClose.bind(this, options._agentKey)); + socket.once("close", onSocketClose.bind(this, agentKey)); } return socket; @@ -369,65 +372,89 @@ Agent.prototype.createConnection = createConnection; Agent.prototype.getName = function getName(options = kEmptyObject) { let name = http.Agent.prototype.getName.$call(this, options); + const { + ca, + cert, + clientCertEngine, + ciphers, + key, + pfx, + rejectUnauthorized, + servername, + host, + minVersion, + maxVersion, + secureProtocol, + crl, + honorCipherOrder, + ecdhCurve, + dhparam, + secureOptions, + sessionIdContext, + sigalgs, + privateKeyIdentifier, + privateKeyEngine, + } = options; + name += ":"; - if (options.ca) name += options.ca; + if (ca) name += ca; name += ":"; - if (options.cert) name += options.cert; + if (cert) name += cert; name += ":"; - if (options.clientCertEngine) name += options.clientCertEngine; + if (clientCertEngine) name += clientCertEngine; name += ":"; - if (options.ciphers) name += options.ciphers; + if (ciphers) name += ciphers; name += ":"; - if (options.key) name += options.key; + if (key) name += key; name += ":"; - if (options.pfx) name += options.pfx; + if (pfx) name += pfx; name += ":"; - if (options.rejectUnauthorized !== undefined) name += options.rejectUnauthorized; + if (rejectUnauthorized !== undefined) name += rejectUnauthorized; name += ":"; - if (options.servername && options.servername !== options.host) name += options.servername; + if (servername && servername !== host) name += servername; name += ":"; - if (options.minVersion) name += options.minVersion; + if (minVersion) name += minVersion; name += ":"; - if (options.maxVersion) name += options.maxVersion; + if (maxVersion) name += maxVersion; name += ":"; - if (options.secureProtocol) name += options.secureProtocol; + if (secureProtocol) name += secureProtocol; name += ":"; - if (options.crl) name += options.crl; + if (crl) name += crl; name += ":"; - if (options.honorCipherOrder !== undefined) name += options.honorCipherOrder; + if (honorCipherOrder !== undefined) name += honorCipherOrder; name += ":"; - if (options.ecdhCurve) name += options.ecdhCurve; + if (ecdhCurve) name += ecdhCurve; name += ":"; - if (options.dhparam) name += options.dhparam; + if (dhparam) name += dhparam; name += ":"; - if (options.secureOptions !== undefined) name += options.secureOptions; + if (secureOptions !== undefined) name += secureOptions; name += ":"; - if (options.sessionIdContext) name += options.sessionIdContext; + if (sessionIdContext) name += sessionIdContext; name += ":"; - if (options.sigalgs) name += JSONStringify(options.sigalgs); + if (sigalgs) name += JSONStringify(sigalgs); name += ":"; - if (options.privateKeyIdentifier) name += options.privateKeyIdentifier; + if (privateKeyIdentifier) name += privateKeyIdentifier; name += ":"; - if (options.privateKeyEngine) name += options.privateKeyEngine; + if (privateKeyEngine) name += privateKeyEngine; return name; }; @@ -441,8 +468,9 @@ Agent.prototype._cacheSession = function _cacheSession(key, session) { if (this.maxCachedSessions === 0) return; // Fast case - update existing entry - if (this._sessionCache.map[key]) { - this._sessionCache.map[key] = session; + const sessionMap = this._sessionCache.map; + if (sessionMap[key]) { + sessionMap[key] = session; return; } diff --git a/src/js/node/net.ts b/src/js/node/net.ts index 06890914808..e6299e42623 100644 --- a/src/js/node/net.ts +++ b/src/js/node/net.ts @@ -227,7 +227,8 @@ function onConnectEnd() { * disconnecting mid-handshake, which Node reports as ECONNRESET. */ function tlsHandshakeError(verifyError) { - if (verifyError && verifyError.code && verifyError.code !== "ECONNRESET") { + const verifyErrorCode = verifyError ? verifyError.code : undefined; + if (verifyErrorCode && verifyErrorCode !== "ECONNRESET") { const reason = verifyError.reason || verifyError.message || "TLS handshake failed"; const err = new Error(reason) as Error & { code?: string; @@ -246,7 +247,7 @@ function tlsHandshakeError(verifyError) { err.reason = match[2]; err.code = `ERR_SSL_${match[2]}`; } else { - err.code = verifyError.code; + err.code = verifyErrorCode; } return err; } @@ -335,8 +336,9 @@ const SocketHandlers: SocketHandler = { if (!self) return; // make sure to disable timeout on usocket and handle on TS side socket.timeout(0); - if (self.timeout) { - self.setTimeout(self.timeout); + const selfTimeout = self.timeout; + if (selfTimeout) { + self.setTimeout(selfTimeout); } self._handle = socket; self.connecting = false; @@ -359,8 +361,9 @@ const SocketHandlers: SocketHandler = { // A TOS value set before the connection existed (setTypeOfService before // connect) is applied to the live handle now. - if (self[kSetTOS] !== undefined && self._handle?.setTypeOfService) { - self._handle.setTypeOfService(self[kSetTOS]); + let handle; + if (self[kSetTOS] !== undefined && (handle = self._handle)?.setTypeOfService) { + handle.setTypeOfService(self[kSetTOS]); } if (!self[kupgraded]) { @@ -419,11 +422,12 @@ const SocketHandlers: SocketHandler = { verifyError = checkServerIdentity(hostname, cert); } } - if (self._requestCert || self._rejectUnauthorized) { + let rejectUnauthorized; + if (self._requestCert || (rejectUnauthorized = self._rejectUnauthorized)) { if (verifyError) { self.authorized = false; self.authorizationError = verifyError.code || verifyError.message; - if (self._rejectUnauthorized) { + if (rejectUnauthorized ?? self._rejectUnauthorized) { self.destroy(verifyError); return; } @@ -484,13 +488,14 @@ function SocketEmitEndNT(self, _err?) { // that race from surfacing as an uncaught exception - the no-listener // case is already a documented silent close. self.once("error", () => {}); - if (_err.code === undefined && typeof _err.errno === "number" && _err.errno !== 0) { + let errErrno; + if (_err.code === undefined && typeof (errErrno = _err.errno) === "number" && errErrno !== 0) { // A codeless close error that still carries the errno (Windows IOCP // delivers some this way): derive the proper code from it, like Node's // errnoException(nread, 'read'). Raw WSA values (-10054, ...) that the // errno table cannot name fall through to the reset shape below instead // of surfacing "Unknown system error N". - const er = new ErrnoException(_err.errno, "read") as Error & { code?: string }; + const er = new ErrnoException(errErrno, "read") as Error & { code?: string }; if (typeof er.code === "string" && /^E[A-Z0-9]+$/.test(er.code)) { self.destroy(er); return; @@ -555,8 +560,9 @@ function consumeSNIResult(state, err, context) { return; } if (context == null) return; - if (typeof context === "object" && context.context) { - state.selected = context.context; + const innerContext = typeof context === "object" ? context.context : undefined; + if (innerContext) { + state.selected = innerContext; } else if (state.server?.[kNativeSecureContextCtor] && context instanceof state.server[kNativeSecureContextCtor]) { state.selected = context; } else { @@ -680,13 +686,14 @@ const ServerHandlers: SocketHandler = { state.suspended = true; return true; } - if (state.failed !== undefined) { + const failed = state.failed; + if (failed !== undefined) { // Stash the error so the handshake-failure handler emits // 'tlsClientError' with it, and return it - the native dispatch // detects an Error return and aborts the handshake, dropping the // connection without a TLS alert the way Node does. stashSNIError(state); - return state.failed; + return failed; } return state.selected; }, @@ -744,8 +751,9 @@ const ServerHandlers: SocketHandler = { // way Node does and tear the connection down. A connection that was // already reported (handshake timeout, explicit destroy) is not // reported a second time when its teardown unwinds the handshake. - if (self._hadError || self.destroyed) { - if (!self.destroyed) self.destroy(); + let alreadyDestroyed; + if (self._hadError || (alreadyDestroyed = self.destroyed)) { + if (!(alreadyDestroyed ?? self.destroyed)) self.destroy(); return; } // An SNICallback that reported an error (or returned an invalid @@ -886,9 +894,10 @@ function onconnection(err, clientHandle) { _socket[kAttach](clientHandle.localPort, clientHandle); - if (self.blockList) { + const blockList = self.blockList; + if (blockList) { const addressType = isIP(clientHandle.remoteAddress); - if (addressType && self.blockList.check(clientHandle.remoteAddress, `ipv${addressType}`)) { + if (addressType && blockList.check(clientHandle.remoteAddress, `ipv${addressType}`)) { const data = { localAddress: _socket.localAddress, localPort: _socket.localPort || clientHandle.localPort, @@ -948,14 +957,15 @@ function onconnection(err, clientHandle) { // accepted socket open forever: report it through tlsClientError after // handshakeTimeout the way Node does. The timer is cleared when the // handshake settles (either way) or the socket closes first. - if (isTLS && self._handshakeTimeout > 0) { + let handshakeTimeout; + if (isTLS && (handshakeTimeout = self._handshakeTimeout) > 0) { const timer = setTimeout(() => { _socket[khandshakeTimer] = undefined; const err = $ERR_TLS_HANDSHAKE_TIMEOUT(); _socket._hadError = true; self.emit("tlsClientError", err, _socket); if (!_socket.destroyed) _socket.destroy(); - }, self._handshakeTimeout); + }, handshakeTimeout); // Node's handshake timer is unref'd: a fully-unref'd server (the // graceful-shutdown pattern) must not be held open by a client that // stalls mid-handshake. @@ -1145,11 +1155,12 @@ const SocketHandlers2: SocketHandler {}; + const handle = this._handle; + if (handle) handle.onread = () => {}; this._handle = null; this._sockname = null; } @@ -1860,11 +1877,12 @@ Socket.prototype._destroy = function _destroy(err, callback) { process.nextTick(emitCloseNT, this, !!err); } - if (this.server) { + const server = this.server; + if (server) { $debug("has server"); - this.server._connections--; - if (this.server._emitCloseIfDrained) { - this.server._emitCloseIfDrained(); + server._connections--; + if (server._emitCloseIfDrained) { + server._emitCloseIfDrained(); } } }; @@ -2375,8 +2393,9 @@ function createConnection(...args) { const options = normalized[0]; const socket = new Socket(options); - if (options.timeout) { - socket.setTimeout(options.timeout); + const optionsTimeout = options.timeout; + if (optionsTimeout) { + socket.setTimeout(optionsTimeout); } return socket.connect(normalized); @@ -2433,7 +2452,8 @@ function lookupAndConnect(self, options) { return; } - if (options.lookup != null) validateFunction(options.lookup, "options.lookup"); + const optionsLookup = options.lookup; + if (optionsLookup != null) validateFunction(optionsLookup, "options.lookup"); if (dns === undefined) dns = require("node:dns"); const dnsopts = { @@ -2446,7 +2466,7 @@ function lookupAndConnect(self, options) { $debug("connect: find host", host, addressType); $debug("connect: dns options", dnsopts); - const lookup = options.lookup || dns.lookup; + const lookup = optionsLookup || dns.lookup; if (dnsopts.family !== 4 && dnsopts.family !== 6 && !localAddress && autoSelectFamily) { $debug("connect: autodetecting", host, port); @@ -2615,8 +2635,9 @@ function internalConnect(self, options, address, port, addressType, localAddress //TLS let connection = self[ksocket]; - if (options.socket) { - connection = options.socket; + const optionsSocket = options.socket; + if (optionsSocket) { + connection = optionsSocket; } let tls = undefined; const bunTLS = self[bunTlsSymbol]; @@ -2636,8 +2657,9 @@ function internalConnect(self, options, address, port, addressType, localAddress self.servername = tls.servername; tls.checkServerIdentity = checkServerIdentity || tls.checkServerIdentity; self[bunTLSConnectOptions] = tls; - if (!connection && tls.socket) { - connection = tls.socket; + let tlsSocket; + if (!connection && (tlsSocket = tls.socket)) { + connection = tlsSocket; } } self.authorized = false; @@ -2764,8 +2786,9 @@ function internalConnectMultiple(context, canceled?) { //TLS let connection = self[ksocket]; - if (context.options.socket) { - connection = context.options.socket; + const contextOptionsSocket = context.options.socket; + if (contextOptionsSocket) { + connection = contextOptionsSocket; } let tls = undefined; const bunTLS = self[bunTlsSymbol]; @@ -2785,8 +2808,9 @@ function internalConnectMultiple(context, canceled?) { self.servername = tls.servername; tls.checkServerIdentity = checkServerIdentity || tls.checkServerIdentity; self[bunTLSConnectOptions] = tls; - if (!connection && tls.socket) { - connection = tls.socket; + let tlsSocket; + if (!connection && (tlsSocket = tls.socket)) { + connection = tlsSocket; } } self.authorized = false; @@ -2911,8 +2935,10 @@ function afterConnect(status, handle, req, readable, writable) { if (readable && !self.isPaused()) self.read(0); } else { let details; - if (req.localAddress && req.localPort) { - details = req.localAddress + ":" + req.localPort; + const localAddress = req.localAddress; + let localPort; + if (localAddress && (localPort = req.localPort)) { + details = localAddress + ":" + localPort; } const ex = new ExceptionWithHostPort(status, "connect", req.address, req.port); if (details) { @@ -2969,8 +2995,10 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w function createConnectionError(req, status) { let details; - if (req.localAddress && req.localPort) { - details = req.localAddress + ":" + req.localPort; + const localAddress = req.localAddress; + let localPort; + if (localAddress && (localPort = req.localPort)) { + details = localAddress + ":" + localPort; } const ex = new ExceptionWithHostPort(status, "connect", req.address, req.port); @@ -3042,11 +3070,12 @@ function Server(options?, connectionListener?) { options.connectionListener = connectionListener; this[bunSocketServerOptions] = options; - if (options.blockList) { - if (!BlockList.isBlockList(options.blockList)) { - throw $ERR_INVALID_ARG_TYPE("options.blockList", "net.BlockList", options.blockList); + const optionsBlockList = options.blockList; + if (optionsBlockList) { + if (!BlockList.isBlockList(optionsBlockList)) { + throw $ERR_INVALID_ARG_TYPE("options.blockList", "net.BlockList", optionsBlockList); } - this.blockList = options.blockList; + this.blockList = optionsBlockList; } } $toClass(Server, "Server", EventEmitter); @@ -3197,8 +3226,9 @@ Server.prototype.listen = function listen(port, hostname, onListen) { readableAll = options.readableAll; writableAll = options.writableAll; - if (typeof options.fd === "number" && options.fd >= 0) { - fd = options.fd; + const optionsFd = options.fd; + if (typeof optionsFd === "number" && optionsFd >= 0) { + fd = optionsFd; port = 0; } @@ -3603,15 +3633,17 @@ function initSocketHandle(self) { self[kended] = false; // Handle creation may be deferred to bind() or connect() time. - if (self._handle) { - self._handle[owner_symbol] = self; + const handle = self._handle; + if (handle) { + handle[owner_symbol] = self; } } function closeSocketHandle(self, isException, isCleanupPending = false) { - $debug("closeSocketHandle", isException, isCleanupPending, !!self._handle); - if (self._handle) { - self._handle.close(); + const handle = self._handle; + $debug("closeSocketHandle", isException, isCleanupPending, !!handle); + if (handle) { + handle.close(); setImmediate(() => { $debug("emit close", isCleanupPending); self.emit("close", isException); @@ -3664,8 +3696,9 @@ function checkBindError(err, port, handle) { if (err === 0 && port > 0 && handle.getsockname) { const out = {}; err = handle.getsockname(out); - if (err === 0 && port !== out.port) { - $debug(`checkBindError, bound to ${out.port} instead of ${port}`); + let outPort; + if (err === 0 && port !== (outPort = out.port)) { + $debug(`checkBindError, bound to ${outPort} instead of ${port}`); const UV_EADDRINUSE = -4091; err = UV_EADDRINUSE; } diff --git a/src/js/node/perf_hooks.ts b/src/js/node/perf_hooks.ts index de20bfc411d..23be4ec2364 100644 --- a/src/js/node/perf_hooks.ts +++ b/src/js/node/perf_hooks.ts @@ -138,10 +138,12 @@ class PerformanceObserverForNodeTypes extends NodePerformanceObserver { let requested; let isTypeMode = false; if (options != null && typeof options === "object") { - if (options.entryTypes !== undefined && Array.isArray(options.entryTypes)) { - requested = options.entryTypes; - } else if (options.type !== undefined) { - requested = [options.type]; + const entryTypes = options.entryTypes; + let type; + if (entryTypes !== undefined && Array.isArray(entryTypes)) { + requested = entryTypes; + } else if ((type = options.type) !== undefined) { + requested = [type]; isTypeMode = true; } } @@ -272,34 +274,37 @@ export default { let highest = Number.MAX_SAFE_INTEGER; let figures = 3; - if (opts.lowest !== undefined) { - if (typeof opts.lowest === "bigint") { - lowest = Number(opts.lowest); - } else if (typeof opts.lowest === "number") { - lowest = opts.lowest; + const lowestOpt = opts.lowest; + if (lowestOpt !== undefined) { + if (typeof lowestOpt === "bigint") { + lowest = Number(lowestOpt); + } else if (typeof lowestOpt === "number") { + lowest = lowestOpt; } else { - throw $ERR_INVALID_ARG_TYPE("options.lowest", ["number", "bigint"], opts.lowest); + throw $ERR_INVALID_ARG_TYPE("options.lowest", ["number", "bigint"], lowestOpt); } } - if (opts.highest !== undefined) { - if (typeof opts.highest === "bigint") { - highest = Number(opts.highest); - } else if (typeof opts.highest === "number") { - highest = opts.highest; + const highestOpt = opts.highest; + if (highestOpt !== undefined) { + if (typeof highestOpt === "bigint") { + highest = Number(highestOpt); + } else if (typeof highestOpt === "number") { + highest = highestOpt; } else { - throw $ERR_INVALID_ARG_TYPE("options.highest", ["number", "bigint"], opts.highest); + throw $ERR_INVALID_ARG_TYPE("options.highest", ["number", "bigint"], highestOpt); } } - if (opts.figures !== undefined) { - if (typeof opts.figures !== "number") { - throw $ERR_INVALID_ARG_TYPE("options.figures", "number", opts.figures); + const figuresOpt = opts.figures; + if (figuresOpt !== undefined) { + if (typeof figuresOpt !== "number") { + throw $ERR_INVALID_ARG_TYPE("options.figures", "number", figuresOpt); } - if (opts.figures < 1 || opts.figures > 5) { - throw $ERR_OUT_OF_RANGE("options.figures", ">= 1 && <= 5", opts.figures); + if (figuresOpt < 1 || figuresOpt > 5) { + throw $ERR_OUT_OF_RANGE("options.figures", ">= 1 && <= 5", figuresOpt); } - figures = opts.figures; + figures = figuresOpt; } // Node.js validation - highest must be >= 2 * lowest diff --git a/src/js/node/querystring.ts b/src/js/node/querystring.ts index cc8a5900c54..6e614079e70 100644 --- a/src/js/node/querystring.ts +++ b/src/js/node/querystring.ts @@ -280,8 +280,11 @@ var require_src = __commonJS((exports, module) => { eq ||= "="; let encode = QueryString.escape; - if (options && typeof options.encodeURIComponent === "function") { - encode = options.encodeURIComponent; + if (options) { + const encodeURIComponentOption = options.encodeURIComponent; + if (typeof encodeURIComponentOption === "function") { + encode = encodeURIComponentOption; + } } const convert = encode === qsEscape ? encodeStringified : encodeStringifiedCustom; @@ -369,19 +372,21 @@ var require_src = __commonJS((exports, module) => { const eqLen = eqCodes.length; let pairs = 1000; - if (options && typeof options.maxKeys === "number") { - // -1 is used in place of a value like Infinity for meaning - // "unlimited pairs" because of additional checks V8 (at least as of v5.4) - // has to do when using variables that contain values like Infinity. Since - // `pairs` is always decremented and checked explicitly for 0, -1 works - // effectively the same as Infinity, while providing a significant - // performance boost. - pairs = options.maxKeys > 0 ? options.maxKeys : -1; - } - let decode = QueryString.unescape; - if (options && typeof options.decodeURIComponent === "function") { - decode = options.decodeURIComponent; + if (options) { + const { maxKeys, decodeURIComponent: decodeURIComponentOption } = options; + if (typeof maxKeys === "number") { + // -1 is used in place of a value like Infinity for meaning + // "unlimited pairs" because of additional checks V8 (at least as of v5.4) + // has to do when using variables that contain values like Infinity. Since + // `pairs` is always decremented and checked explicitly for 0, -1 works + // effectively the same as Infinity, while providing a significant + // performance boost. + pairs = maxKeys > 0 ? maxKeys : -1; + } + if (typeof decodeURIComponentOption === "function") { + decode = decodeURIComponentOption; + } } const customDecode = decode !== qsUnescape; diff --git a/src/js/node/readline.ts b/src/js/node/readline.ts index 6298a77e4ec..dc1e52d686f 100644 --- a/src/js/node/readline.ts +++ b/src/js/node/readline.ts @@ -1018,19 +1018,21 @@ function onEnd() { function onTermEnd() { debug("onTermEnd"); - if (typeof this.line === "string" && this.line.length > 0) { - this.emit("line", this.line); + const line = this.line; + if (typeof line === "string" && line.length > 0) { + this.emit("line", line); } this.close(); } function onKeyPress(s, key) { this[kTtyWrite](s, key); - if (key && key.sequence) { + const sequence = key ? key.sequence : undefined; + if (sequence) { // If the keySeq is half of a surrogate pair // (>= 0xd800 and <= 0xdfff), refresh the line so // the character is displayed appropriately. - var ch = StringPrototypeCodePointAt.$call(key.sequence, 0)!; + var ch = StringPrototypeCodePointAt.$call(sequence, 0)!; if (ch >= 0xd800 && ch <= 0xdfff) this[kRefreshLine](); } } @@ -1210,7 +1212,8 @@ var _Interface = class Interface extends InterfaceConstructor { } get columns() { var output = this.output; - if (output && output.columns) return output.columns; + var columns = output ? output.columns : undefined; + if (columns) return columns; return Infinity; } @@ -1297,31 +1300,35 @@ var _Interface = class Interface extends InterfaceConstructor { [kWriteToOutput](stringToWrite) { validateString(stringToWrite, "stringToWrite"); - if (this.output !== null && this.output !== undefined) { - this.output.write(stringToWrite); + const output = this.output; + if (output !== null && output !== undefined) { + output.write(stringToWrite); } } [kAddHistory]() { - if (this.line.length === 0) return ""; + const line = this.line; + if (line.length === 0) return ""; // If the history is disabled then return the line - if (this.historySize === 0) return this.line; + if (this.historySize === 0) return line; // If the trimmed line is empty then return the line - if (StringPrototypeTrim.$call(this.line).length === 0) return this.line; + if (StringPrototypeTrim.$call(line).length === 0) return line; - if (this.history.length === 0 || this.history[0] !== this.line) { + const history = this.history; + const historyEmpty = history.length === 0; + if (historyEmpty || history[0] !== line) { if (this.removeHistoryDuplicates) { // Remove older history line if identical to new one - var dupIndex = ArrayPrototypeIndexOf.$call(this.history, this.line); - if (dupIndex !== -1) ArrayPrototypeSplice.$call(this.history, dupIndex, 1); + var dupIndex = ArrayPrototypeIndexOf.$call(history, line); + if (dupIndex !== -1) ArrayPrototypeSplice.$call(history, dupIndex, 1); } - ArrayPrototypeUnshift.$call(this.history, this.line); + ArrayPrototypeUnshift.$call(history, line); // Only store so many - if (this.history.length > this.historySize) ArrayPrototypePop.$call(this.history); + if (history.length > this.historySize) ArrayPrototypePop.$call(history); } this.historyIndex = -1; @@ -1329,12 +1336,12 @@ var _Interface = class Interface extends InterfaceConstructor { // The listener could change the history object, possibly // to remove the last added entry if it is sensitive and should // not be persisted in the history, like a password - var line = this.history[0]; + const latest = this.history[0]; // Emit history event to notify listeners of update this.emit("history", this.history); - return line; + return latest; } [kRefreshLine]() { @@ -1480,9 +1487,11 @@ var _Interface = class Interface extends InterfaceConstructor { [kInsertString](c) { this[kBeforeEdit](this.line, this.cursor); - if (this.cursor < this.line.length) { - var beg = StringPrototypeSlice.$call(this.line, 0, this.cursor); - var end = StringPrototypeSlice.$call(this.line, this.cursor, this.line.length); + const line = this.line; + const lineLength = line.length; + if (this.cursor < lineLength) { + var beg = StringPrototypeSlice.$call(line, 0, this.cursor); + var end = StringPrototypeSlice.$call(line, this.cursor, lineLength); this.line = beg + c + end; this.cursor += c.length; this[kRefreshLine](); @@ -1524,8 +1533,9 @@ var _Interface = class Interface extends InterfaceConstructor { // If there is a common prefix to all matches, then apply that portion. var prefix = commonPrefix(ArrayPrototypeFilter.$call(completions, e => e !== "")); - if (StringPrototypeStartsWith.$call(prefix, completeOn) && prefix.length > completeOn.length) { - this[kInsertString](StringPrototypeSlice.$call(prefix, completeOn.length)); + var completeOnLength = completeOn.length; + if (StringPrototypeStartsWith.$call(prefix, completeOn) && prefix.length > completeOnLength) { + this[kInsertString](StringPrototypeSlice.$call(prefix, completeOnLength)); return; } else if (!StringPrototypeStartsWith.$call(completeOn, prefix)) { this.line = @@ -1578,10 +1588,11 @@ var _Interface = class Interface extends InterfaceConstructor { } [kWordLeft]() { - if (this.cursor > 0) { + const cursor = this.cursor; + if (cursor > 0) { // Reverse the string and match a word near beginning // to avoid quadratic time complexity - var leading = StringPrototypeSlice.$call(this.line, 0, this.cursor); + var leading = StringPrototypeSlice.$call(this.line, 0, cursor); var reversed = ArrayPrototypeJoin.$call(ArrayPrototypeReverse.$call(ArrayFrom(leading)), ""); var match = RegExpPrototypeExec.$call(/^\s*(?:[^\w\s]+|\w+)?/, reversed); this[kMoveCursor](-match[0].length); @@ -1589,21 +1600,25 @@ var _Interface = class Interface extends InterfaceConstructor { } [kWordRight]() { - if (this.cursor < this.line.length) { - var trailing = StringPrototypeSlice.$call(this.line, this.cursor); + const cursor = this.cursor; + const line = this.line; + if (cursor < line.length) { + var trailing = StringPrototypeSlice.$call(line, cursor); var match = RegExpPrototypeExec.$call(/^(?:\s+|[^\w\s]+|\w+)\s*/, trailing); this[kMoveCursor](match[0].length); } } [kDeleteLeft]() { - if (this.cursor > 0 && this.line.length > 0) { - this[kBeforeEdit](this.line, this.cursor); + const cursor = this.cursor; + const line = this.line; + const lineLength = line.length; + if (cursor > 0 && lineLength > 0) { + this[kBeforeEdit](line, cursor); // The number of UTF-16 units comprising the character to the left - var charSize = charLengthLeft(this.line, this.cursor); + var charSize = charLengthLeft(line, cursor); this.line = - StringPrototypeSlice.$call(this.line, 0, this.cursor - charSize) + - StringPrototypeSlice.$call(this.line, this.cursor, this.line.length); + StringPrototypeSlice.$call(line, 0, cursor - charSize) + StringPrototypeSlice.$call(line, cursor, lineLength); this.cursor -= charSize; this[kRefreshLine](); @@ -1611,13 +1626,15 @@ var _Interface = class Interface extends InterfaceConstructor { } [kDeleteRight]() { - if (this.cursor < this.line.length) { - this[kBeforeEdit](this.line, this.cursor); + const cursor = this.cursor; + const line = this.line; + const lineLength = line.length; + if (cursor < lineLength) { + this[kBeforeEdit](line, cursor); // The number of UTF-16 units comprising the character to the left - var charSize = charLengthAt(this.line, this.cursor); + var charSize = charLengthAt(line, cursor); this.line = - StringPrototypeSlice.$call(this.line, 0, this.cursor) + - StringPrototypeSlice.$call(this.line, this.cursor + charSize, this.line.length); + StringPrototypeSlice.$call(line, 0, cursor) + StringPrototypeSlice.$call(line, cursor + charSize, lineLength); this[kRefreshLine](); } } @@ -1638,12 +1655,13 @@ var _Interface = class Interface extends InterfaceConstructor { } [kDeleteWordRight]() { - if (this.cursor < this.line.length) { - this[kBeforeEdit](this.line, this.cursor); - var trailing = StringPrototypeSlice.$call(this.line, this.cursor); + const cursor = this.cursor; + const line = this.line; + if (cursor < line.length) { + this[kBeforeEdit](line, cursor); + var trailing = StringPrototypeSlice.$call(line, cursor); var match = RegExpPrototypeExec.$call(/^(?:\s+|\W+|\w+)\s*/, trailing); - this.line = - StringPrototypeSlice.$call(this.line, 0, this.cursor) + StringPrototypeSlice.$call(trailing, match[0].length); + this.line = StringPrototypeSlice.$call(line, 0, cursor) + StringPrototypeSlice.$call(trailing, match[0].length); this[kRefreshLine](); } } @@ -1773,20 +1791,22 @@ var _Interface = class Interface extends InterfaceConstructor { } [kHistoryPrev]() { - if (this.historyIndex < this.history.length && this.history.length) { + const history = this.history; + const historyLength = history.length; + if (this.historyIndex < historyLength && historyLength) { this[kBeforeEdit](this.line, this.cursor); var search = this[kSubstringSearch] || ""; var index = this.historyIndex + 1; while ( - index < this.history.length && - (!StringPrototypeStartsWith.$call(this.history[index], search) || this.line === this.history[index]) + index < historyLength && + (!StringPrototypeStartsWith.$call(history[index], search) || this.line === history[index]) ) { index++; } - if (index === this.history.length) { + if (index === historyLength) { this.line = search; } else { - this.line = this.history[index]; + this.line = history[index]; } this.historyIndex = index; this.cursor = this.line.length; // Set cursor to end of line. @@ -1851,10 +1871,11 @@ var _Interface = class Interface extends InterfaceConstructor { this.cursor += dx; // Bounds check + let lineLength; if (this.cursor < 0) { this.cursor = 0; - } else if (this.cursor > this.line.length) { - this.cursor = this.line.length; + } else if (this.cursor > (lineLength = this.line.length)) { + this.cursor = lineLength; } var newPos = this.getCursorPos(); diff --git a/src/js/node/tls.ts b/src/js/node/tls.ts index efef4c2713e..a1f7608e08a 100644 --- a/src/js/node/tls.ts +++ b/src/js/node/tls.ts @@ -391,8 +391,9 @@ function validateSecureContextOptions(options) { throw $ERR_TLS_INVALID_PROTOCOL_VERSION(String(maxVersion), "maximum"); if (ticketKeys !== undefined && ticketKeys !== null) { validateBuffer(ticketKeys, "options.ticketKeys"); - if (ticketKeys.byteLength !== 48) { - throw $ERR_INVALID_ARG_VALUE("options.ticketKeys", ticketKeys.byteLength, "must be exactly 48 bytes"); + const ticketKeysByteLength = ticketKeys.byteLength; + if (ticketKeysByteLength !== 48) { + throw $ERR_INVALID_ARG_VALUE("options.ticketKeys", ticketKeysByteLength, "must be exactly 48 bytes"); } } // Negative session timeouts are rejected (min 0), matching Node — newer @@ -581,24 +582,27 @@ function checkServerIdentity(hostname, cert) { if (net.isIP(hostname)) { valid = ArrayPrototypeIncludes.$call(ips, canonicalizeIP(hostname)); if (!valid) reason = `IP: ${hostname} is not in the cert's list: ` + ArrayPrototypeJoin.$call(ips, ", "); - } else if (dnsNames.length > 0 || subject?.CN) { - const hostParts = splitHost(hostname); - const wildcard = pattern => check(hostParts, pattern, true); - - if (dnsNames.length > 0) { - valid = ArrayPrototypeSome.$call(dnsNames, wildcard); - if (!valid) reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; - } else { - // Match against Common Name only if no supported identifiers exist. - const cn = subject.CN; + } else { + const hasDnsNames = dnsNames.length > 0; + if (hasDnsNames || subject?.CN) { + const hostParts = splitHost(hostname); + const wildcard = pattern => check(hostParts, pattern, true); + + if (hasDnsNames) { + valid = ArrayPrototypeSome.$call(dnsNames, wildcard); + if (!valid) reason = `Host: ${hostname}. is not in the cert's altnames: ${altNames}`; + } else { + // Match against Common Name only if no supported identifiers exist. + const cn = subject.CN; - if (Array.isArray(cn)) valid = ArrayPrototypeSome.$call(cn, wildcard); - else if (cn) valid = wildcard(cn); + if (Array.isArray(cn)) valid = ArrayPrototypeSome.$call(cn, wildcard); + else if (cn) valid = wildcard(cn); - if (!valid) reason = `Host: ${hostname}. is not cert's CN: ${cn}`; + if (!valid) reason = `Host: ${hostname}. is not cert's CN: ${cn}`; + } + } else { + reason = "Cert does not contain a DNS name"; } - } else { - reason = "Cert does not contain a DNS name"; } if (!valid) { return $ERR_TLS_CERT_ALTNAME_INVALID(reason, hostname, cert); @@ -679,15 +683,13 @@ function processPfxOptions(options) { for (const entry of entries) { let buf = entry; let passphrase = out.passphrase; - if ( - entry != null && - typeof entry === "object" && - !Buffer.isBuffer(entry) && - !$isTypedArrayView(entry) && - entry.buf !== undefined - ) { - buf = entry.buf; - if (entry.passphrase !== undefined) passphrase = entry.passphrase; + if (entry != null && typeof entry === "object" && !Buffer.isBuffer(entry) && !$isTypedArrayView(entry)) { + const entryBuf = entry.buf; + if (entryBuf !== undefined) { + buf = entryBuf; + const entryPassphrase = entry.passphrase; + if (entryPassphrase !== undefined) passphrase = entryPassphrase; + } } const parsed = NativeSecureContext.parsePkcs12(buf, passphrase); keys.push(parsed.key); @@ -696,7 +698,8 @@ function processPfxOptions(options) { // via addCACert on top of the default roots); folding it into the `ca` // option would instead REPLACE the trust store and break verification // against the default/NODE_EXTRA_CA_CERTS roots for pfx-only clients. - if (parsed.ca) pfxCAs.push(parsed.ca); + const parsedCA = parsed.ca; + if (parsedCA) pfxCAs.push(parsedCA); } out.key = keys.length === 1 ? keys[0] : keys; out.cert = certs.length === 1 ? certs[0] : certs; @@ -717,18 +720,22 @@ function newNativeSecureContext(options, cached = true) { // ALPN protocols given as an array of strings are converted to the // length-prefixed wire format before crossing into native, the way Node's // convertALPNProtocols normalizes them on the socket options. - if (Array.isArray(options.ALPNProtocols)) { + const ALPNProtocols = options.ALPNProtocols; + if (Array.isArray(ALPNProtocols)) { const normalized = {}; - convertALPNProtocols(options.ALPNProtocols, normalized); + convertALPNProtocols(ALPNProtocols, normalized); options = { ...options, ALPNProtocols: normalized.ALPNProtocols }; } - if (options && (!options.key || !options.cert || !options.ca)) { - options = { - ...options, - key: options.key || null, - cert: options.cert || null, - ca: options.ca || null, - }; + if (options) { + const { key, cert, ca } = options; + if (!key || !cert || !ca) { + options = { + ...options, + key: key || null, + cert: cert || null, + ca: ca || null, + }; + } } if (options) { // Read each option once. Translate minVersion/maxVersion/secureProtocol to @@ -772,27 +779,28 @@ var InternalSecureContext = class SecureContext { } if (options) { validateSecureContextOptions(options); - if (options.cert) throwOnInvalidTLSArray("options.cert", options.cert); - if (options.key) throwOnInvalidTLSArray("options.key", options.key); - if (options.ca) throwOnInvalidTLSArray("options.ca", options.ca); + const cert = options.cert; + if (cert) throwOnInvalidTLSArray("options.cert", cert); + const key = options.key; + if (key) throwOnInvalidTLSArray("options.key", key); + const ca = options.ca; + if (ca) throwOnInvalidTLSArray("options.ca", ca); if (options.servername != null && typeof options.servername !== "string") throw new TypeError("servername argument must be an string"); if (options.secureOptions != null && typeof options.secureOptions !== "number") throw new TypeError("secureOptions argument must be an number"); - if (!$isUndefinedOrNull(options.privateKeyIdentifier)) { - if ($isUndefinedOrNull(options.privateKeyEngine)) - throw $ERR_INVALID_ARG_VALUE("options.privateKeyEngine", options.privateKeyEngine); - if (typeof options.privateKeyEngine !== "string") - throw $ERR_INVALID_ARG_TYPE( - "options.privateKeyEngine", - ["string", "null", "undefined"], - options.privateKeyEngine, - ); - if (typeof options.privateKeyIdentifier !== "string") + const privateKeyIdentifier = options.privateKeyIdentifier; + if (!$isUndefinedOrNull(privateKeyIdentifier)) { + const privateKeyEngine = options.privateKeyEngine; + if ($isUndefinedOrNull(privateKeyEngine)) + throw $ERR_INVALID_ARG_VALUE("options.privateKeyEngine", privateKeyEngine); + if (typeof privateKeyEngine !== "string") + throw $ERR_INVALID_ARG_TYPE("options.privateKeyEngine", ["string", "null", "undefined"], privateKeyEngine); + if (typeof privateKeyIdentifier !== "string") throw $ERR_INVALID_ARG_TYPE( "options.privateKeyIdentifier", ["string", "null", "undefined"], - options.privateKeyIdentifier, + privateKeyIdentifier, ); } } @@ -923,10 +931,11 @@ function TLSSocket(socket?, options?) { this.secureConnecting = true; this._secureEstablished = false; this._securePending = true; - if (options.checkServerIdentity !== undefined) { - validateFunction(options.checkServerIdentity, "options.checkServerIdentity"); + const checkServerIdentityOption = options.checkServerIdentity; + if (checkServerIdentityOption !== undefined) { + validateFunction(checkServerIdentityOption, "options.checkServerIdentity"); } - this[kcheckServerIdentity] = options.checkServerIdentity || checkServerIdentity; + this[kcheckServerIdentity] = checkServerIdentityOption || checkServerIdentity; this[ksession] = options.session || null; // `new tls.TLSSocket(socket, { isServer: true })`: drive the server-side TLS @@ -1038,8 +1047,9 @@ TLSSocket.prototype.renegotiate = function renegotiate(options, callback) { let requestCert = !!this._requestCert; let rejectUnauthorized = !!this._rejectUnauthorized; - if (options.requestCert !== undefined) requestCert = !!options.requestCert; - if (options.rejectUnauthorized !== undefined) rejectUnauthorized = !!options.rejectUnauthorized; + const { requestCert: requestCertOption, rejectUnauthorized: rejectUnauthorizedOption } = options; + if (requestCertOption !== undefined) requestCert = !!requestCertOption; + if (rejectUnauthorizedOption !== undefined) rejectUnauthorized = !!rejectUnauthorizedOption; if (requestCert !== this._requestCert || rejectUnauthorized !== this._rejectUnauthorized) { socket.setVerifyMode?.(requestCert, rejectUnauthorized); this._requestCert = requestCert; @@ -1113,12 +1123,12 @@ TLSSocket.prototype.setSession = function setSession(session) { }; TLSSocket.prototype.getPeerCertificate = function getPeerCertificate(detailed) { - if (this._handle) { + const handle = this._handle; + if (handle) { // The native parameter means "abbreviated" - the inverse of Node's // `detailed`. Detailed requests get the whole chain with // issuerCertificate links; everything else gets just the leaf. - const cert = - arguments.length < 1 ? this._handle.getPeerCertificate?.() : this._handle.getPeerCertificate?.(!detailed); + const cert = arguments.length < 1 ? handle.getPeerCertificate?.() : handle.getPeerCertificate?.(!detailed); if (cert) { return translatePeerCertificate(cert); } @@ -1153,8 +1163,9 @@ TLSSocket.prototype.getPeerX509Certificate = function getPeerX509Certificate() { if (cached) return cached; const x509 = new X509Certificate(chainCert.raw); seen.set(chainCert, x509); - if (chainCert.issuerCertificate && chainCert.issuerCertificate !== chainCert) { - const issuer = toX509(chainCert.issuerCertificate); + const issuerCertificate = chainCert.issuerCertificate; + if (issuerCertificate && issuerCertificate !== chainCert) { + const issuer = toX509(issuerCertificate); if (issuer) { Object.defineProperty(x509, "issuerCertificate", { __proto__: null, @@ -1250,10 +1261,11 @@ function Server(options, secureConnectionListener): void { if (!(context instanceof InternalSecureContext)) { context = new InternalSecureContext(context); } - if (this._handle) { + const handle = this._handle; + if (handle) { // Pass the native SSL_CTX wrapper, not the JS InternalSecureContext — // the native side detects it via SecureContext.fromJS and up_refs. - addServerName(this._handle, hostname, context.context); + addServerName(handle, hostname, context.context); } else { if (!contexts) contexts = new Map(); contexts.set(hostname, context); @@ -1297,11 +1309,12 @@ function Server(options, secureConnectionListener): void { // type differs from its own index-paired certificate. This is a // best-effort check - the native loader at listen time remains the // authority and still rejects configurations that pass it. - if (Array.isArray(key) && key.length > 1 && cert) { + const keyLength = Array.isArray(key) ? key.length : 0; + if (keyLength > 1 && cert) { const certs = Array.isArray(cert) ? cert : [cert]; try { const { createPrivateKey, X509Certificate } = require("node:crypto"); - for (let i = 0; i < key.length; i++) { + for (let i = 0; i < keyLength; i++) { const k = key[i]; if (typeof k !== "string" && !$isTypedArrayView(k)) continue; const pairedCert = certs[i < certs.length ? i : certs.length - 1]; @@ -1377,12 +1390,13 @@ function Server(options, secureConnectionListener): void { this._rejectUnauthorized = rejectUnauthorized; } else this._rejectUnauthorized = rejectUnauthorizedDefault(); - if (typeof options.ciphers !== "undefined") { - if (typeof options.ciphers !== "string") { - throw $ERR_INVALID_ARG_TYPE("options.ciphers", "string", options.ciphers); + const ciphers = options.ciphers; + if (typeof ciphers !== "undefined") { + if (typeof ciphers !== "string") { + throw $ERR_INVALID_ARG_TYPE("options.ciphers", "string", ciphers); } - validateCiphers(options.ciphers); + validateCiphers(ciphers); } // Unconditional so an omitted `ciphers` clears the previous value. this.ciphers = options.ciphers; @@ -1536,8 +1550,9 @@ function connect(...args) { // the net.createConnection factory does), so tls.connect applies it // explicitly, exactly like Node's tls connect. // https://github.com/nodejs/node/blob/614050b657e9757c1097aa85f92f2cb51149dc0d/lib/internal/tls/wrap.js#L1791 - if (options.timeout) { - tlssock.setTimeout(options.timeout); + const timeout = options.timeout; + if (timeout) { + tlssock.setTimeout(timeout); } return tlssock.connect(normal); } diff --git a/src/js/node/url.ts b/src/js/node/url.ts index 9887adcef3e..29b0dba646d 100644 --- a/src/js/node/url.ts +++ b/src/js/node/url.ts @@ -415,10 +415,10 @@ Url.prototype.parse = function parse(url: string, parseQueryString?: boolean, sl } // to support http.request - if (this.pathname || this.search) { - var p = this.pathname || ""; - var s = this.search || ""; - this.path = p + s; + const pathname = this.pathname; + let search; + if (pathname || (search = this.search)) { + this.path = (pathname || "") + ((search ?? this.search) || ""); } // finally, reconstruct the href based on what has been validated. @@ -496,17 +496,21 @@ Url.prototype.format = function format() { host = "", query = ""; - if (this.host) { - host = auth + this.host; - } else if (this.hostname) { - host = auth + (this.hostname.indexOf(":") === -1 ? this.hostname : "[" + this.hostname + "]"); - if (this.port) { - host += ":" + this.port; + const thisHost = this.host; + let thisHostname; + if (thisHost) { + host = auth + thisHost; + } else if ((thisHostname = this.hostname)) { + host = auth + (thisHostname.indexOf(":") === -1 ? thisHostname : "[" + thisHostname + "]"); + const thisPort = this.port; + if (thisPort) { + host += ":" + thisPort; } } - if (this.query && typeof this.query === "object" && Object.keys(this.query).length) { - query = new URLSearchParams(this.query).toString(); + const thisQuery = this.query; + if (thisQuery && typeof thisQuery === "object" && Object.keys(thisQuery).length) { + query = new URLSearchParams(thisQuery).toString(); } var search = this.search || (query && "?" + query) || ""; @@ -605,7 +609,8 @@ Url.prototype.resolveObject = function resolveObject(relative) { return result; } - if (relative.protocol && relative.protocol !== result.protocol) { + const relativeProtocol = relative.protocol; + if (relativeProtocol && relativeProtocol !== result.protocol) { /* * if it's a known url protocol, then changing * the protocol does weird things @@ -616,7 +621,7 @@ Url.prototype.resolveObject = function resolveObject(relative) { * because that's known to be hostless. * anything else is assumed to be absolute. */ - if (!slashedProtocol[relative.protocol]) { + if (!slashedProtocol[relativeProtocol]) { var keys = Object.keys(relative); for (var v = 0; v < keys.length; v++) { var k = keys[v]; @@ -626,11 +631,11 @@ Url.prototype.resolveObject = function resolveObject(relative) { return result; } - result.protocol = relative.protocol; + result.protocol = relativeProtocol; if ( !relative.host && - !(relative.protocol === "file" || relative.protocol === "file:") && - !hostlessProtocol[relative.protocol] + !(relativeProtocol === "file" || relativeProtocol === "file:") && + !hostlessProtocol[relativeProtocol] ) { let relPath = (relative.pathname || "").split("/"); while (relPath.length && !(relative.host = relPath.shift())) {} @@ -650,10 +655,10 @@ Url.prototype.resolveObject = function resolveObject(relative) { result.hostname = relative.hostname || relative.host; result.port = relative.port; // to support http.request - if (result.pathname || result.search) { - var p = result.pathname || ""; - var s = result.search || ""; - result.path = p + s; + const resultPathname = result.pathname; + let resultSearch; + if (resultPathname || (resultSearch = result.search)) { + result.path = (resultPathname || "") + ((resultSearch ?? result.search) || ""); } result.slashes = result.slashes || relative.slashes; result.href = result.format(); @@ -678,20 +683,22 @@ Url.prototype.resolveObject = function resolveObject(relative) { if (psychotic) { result.hostname = ""; result.port = null; - if (result.host) { - if (srcPath[0] === "") srcPath[0] = result.host; - else srcPath.unshift(result.host); + const resultHost = result.host; + if (resultHost) { + if (srcPath[0] === "") srcPath[0] = resultHost; + else srcPath.unshift(resultHost); } result.host = ""; if (relative.protocol) { relative.hostname = null; relative.port = null; result.auth = null; - if (relative.host) { + const relativeHost = relative.host; + if (relativeHost) { if (relPath[0] === "") { - relPath[0] = relative.host; + relPath[0] = relativeHost; } else { - relPath.unshift(relative.host); + relPath.unshift(relativeHost); } } relative.host = null; @@ -701,14 +708,16 @@ Url.prototype.resolveObject = function resolveObject(relative) { if (isRelAbs) { // it's absolute. - if (relative.host || relative.host === "") { - if (result.host !== relative.host) result.auth = null; - result.host = relative.host; + const relativeHost = relative.host; + if (relativeHost || relativeHost === "") { + if (result.host !== relativeHost) result.auth = null; + result.host = relativeHost; result.port = relative.port; } - if (relative.hostname || relative.hostname === "") { - if (result.hostname !== relative.hostname) result.auth = null; - result.hostname = relative.hostname; + const relativeHostname = relative.hostname; + if (relativeHostname || relativeHostname === "") { + if (result.hostname !== relativeHostname) result.auth = null; + result.hostname = relativeHostname; } result.search = relative.search; result.query = relative.query; @@ -724,35 +733,40 @@ Url.prototype.resolveObject = function resolveObject(relative) { srcPath = srcPath.concat(relPath); result.search = relative.search; result.query = relative.query; - } else if (relative.search != null && relative.search !== undefined) { - /* - * just pull out the search. - * like href='?foo'. - * Put this after the other two cases because it simplifies the booleans - */ - if (psychotic) { - result.hostname = result.host = srcPath.shift(); + } else { + const relativeSearch = relative.search; + if (relativeSearch != null && relativeSearch !== undefined) { /* - * occationaly the auth can get stuck only in host - * this especially happens in cases like - * url.resolveObject('mailto:local1@domain1', 'local2@domain2') + * just pull out the search. + * like href='?foo'. + * Put this after the other two cases because it simplifies the booleans */ - var authInHost = result.host && result.host.indexOf("@") > 0 ? result.host.split("@") : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.hostname = result.host = authInHost.shift(); + if (psychotic) { + result.hostname = result.host = srcPath.shift(); + /* + * occationaly the auth can get stuck only in host + * this especially happens in cases like + * url.resolveObject('mailto:local1@domain1', 'local2@domain2') + */ + var authInHost = result.host && result.host.indexOf("@") > 0 ? result.host.split("@") : false; + if (authInHost) { + result.auth = authInHost.shift(); + result.hostname = result.host = authInHost.shift(); + } } + result.search = relativeSearch; + result.query = relative.query; + // to support http.request + const resultPathname = result.pathname; + const resultSearch = result.search; + if (resultPathname !== null || resultSearch !== null) { + result.path = + (resultPathname ? resultPathname : "") + // force line break + (resultSearch ? resultSearch : ""); + } + result.href = result.format(); + return result; } - result.search = relative.search; - result.query = relative.query; - // to support http.request - if (result.pathname !== null || result.search !== null) { - result.path = - (result.pathname ? result.pathname : "") + // force line break - (result.search ? result.search : ""); - } - result.href = result.format(); - return result; } if (!srcPath.length) { @@ -762,8 +776,9 @@ Url.prototype.resolveObject = function resolveObject(relative) { */ result.pathname = null; // to support http.request - if (result.search) { - result.path = "/" + result.search; + const resultSearch = result.search; + if (resultSearch) { + result.path = "/" + resultSearch; } else { result.path = null; } @@ -847,10 +862,13 @@ Url.prototype.resolveObject = function resolveObject(relative) { } // to support request.http - if (result.pathname !== null || result.search !== null) { + const resultPathname = result.pathname; + let resultSearch; + if (resultPathname !== null || (resultSearch = result.search) !== null) { + resultSearch ??= result.search; // prettier-ignore - result.path = (result.pathname ? result.pathname : "") + - (result.search ? result.search : ""); + result.path = (resultPathname ? resultPathname : "") + + (resultSearch ? resultSearch : ""); } result.auth = relative.auth || result.auth; result.slashes = result.slashes || relative.slashes; diff --git a/src/js/node/util.ts b/src/js/node/util.ts index a48cd87c05d..da282f6f359 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -34,8 +34,9 @@ const stripVTControlCharacters = utl.stripVTControlCharacters; var debugs = {}; var debugEnvRegex = /^$/; -if (process.env.NODE_DEBUG) { - debugEnv = process.env.NODE_DEBUG; +const NODE_DEBUG = process.env.NODE_DEBUG; +if (NODE_DEBUG) { + debugEnv = NODE_DEBUG; debugEnv = debugEnv .replace(/[|\\{}()[\]^$+?.]/g, "\\$&") .replace(/\*/g, ".*") @@ -139,8 +140,9 @@ var inherits = function inherits(ctor, superCtor) { throw $ERR_INVALID_ARG_TYPE("superCtor", "function", superCtor); } - if (superCtor.prototype === undefined) { - throw $ERR_INVALID_ARG_TYPE("superCtor.prototype", "object", superCtor.prototype); + const superCtorPrototype = superCtor.prototype; + if (superCtorPrototype === undefined) { + throw $ERR_INVALID_ARG_TYPE("superCtor.prototype", "object", superCtorPrototype); } Object.defineProperty(ctor, "super_", { // @ts-ignore diff --git a/src/js/node/vm.ts b/src/js/node/vm.ts index 24c4be09aa3..5b189b5a66b 100644 --- a/src/js/node/vm.ts +++ b/src/js/node/vm.ts @@ -463,8 +463,11 @@ function importModuleDynamicallyWrap(importModuleDynamically) { function getConstructorOf(obj) { while (obj) { const descriptor = ObjectGetOwnPropertyDescriptor(obj, "constructor"); - if (descriptor !== undefined && typeof descriptor.value === "function" && descriptor.value.name !== "") { - return descriptor.value; + if (descriptor !== undefined) { + const value = descriptor.value; + if (typeof value === "function" && value.name !== "") { + return value; + } } obj = ObjectGetPrototypeOf(obj); diff --git a/src/js/node/wasi.ts b/src/js/node/wasi.ts index ba828d7f0c5..b437fb52192 100644 --- a/src/js/node/wasi.ts +++ b/src/js/node/wasi.ts @@ -1250,9 +1250,10 @@ var require_wasi = __commonJS({ let position = IS_STDIN || stats.offset === void 0 ? null : Number(stats.offset); let rr = 0; if (IS_STDIN) { - if (this.getStdin != null) { + const getStdin = this.getStdin; + if (getStdin != null) { if (this.stdinBuffer == null) { - this.stdinBuffer = this.getStdin(); + this.stdinBuffer = getStdin.$call(this); } if (this.stdinBuffer != null) { rr = this.stdinBuffer.copy(iov); @@ -1534,22 +1535,16 @@ var require_wasi = __commonJS({ noflags |= nodeFsConstants.O_APPEND; } if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) { - if (nodeFsConstants.O_DSYNC) { - noflags |= nodeFsConstants.O_DSYNC; - } else { - noflags |= nodeFsConstants.O_SYNC; - } + const O_DSYNC = nodeFsConstants.O_DSYNC; + noflags |= O_DSYNC ? O_DSYNC : nodeFsConstants.O_SYNC; neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC; } if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) { noflags |= nodeFsConstants.O_NONBLOCK; } if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) { - if (nodeFsConstants.O_RSYNC) { - noflags |= nodeFsConstants.O_RSYNC; - } else { - noflags |= nodeFsConstants.O_SYNC; - } + const O_RSYNC = nodeFsConstants.O_RSYNC; + noflags |= O_RSYNC ? O_RSYNC : nodeFsConstants.O_SYNC; neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC; } if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) { @@ -1821,13 +1816,14 @@ var require_wasi = __commonJS({ if (waitTimeNs > 0) { waitTimeNs -= Bun.nanoseconds() - timeOrigin; if (waitTimeNs >= 1e6) { - if (this.sleep == null && !warnedAboutSleep) { + const sleep = this.sleep; + if (sleep == null && !warnedAboutSleep) { warnedAboutSleep = true; console.log("(100% cpu burning waiting for stdin: please define a way to sleep!) "); } - if (this.sleep != null) { + if (sleep != null) { const ms = nsToMs(waitTimeNs); - this.sleep(ms); + sleep.$call(this, ms); } else { const end = BigInt(bindings.hrtime()) + waitTimeNs; while (BigInt(bindings.hrtime()) < end) {} @@ -2002,8 +1998,9 @@ var require_wasi = __commonJS({ } } initWasiFdInfo() { - if (this.env["WASI_FD_INFO"] != null) { - const fdInfo = JSON.parse(this.env["WASI_FD_INFO"]); + const env = this.env; + if (env["WASI_FD_INFO"] != null) { + const fdInfo = JSON.parse(env["WASI_FD_INFO"]); for (const wasi_fd in fdInfo) { console.log(wasi_fd); const fd = parseInt(wasi_fd); diff --git a/src/js/node/worker_threads.ts b/src/js/node/worker_threads.ts index dc6c96a5ca2..fd14bfb8841 100644 --- a/src/js/node/worker_threads.ts +++ b/src/js/node/worker_threads.ts @@ -606,8 +606,9 @@ class Worker extends EventEmitter { let error = event?.error; // if the thrown value serialized successfully, the message will be empty // if not the message is the actual error - if (event.message !== "") { - error = new Error(event.message, { cause: event }); + const message = event.message; + if (message !== "") { + error = new Error(message, { cause: event }); const stack = event?.stack; if (stack) { error.stack = stack; diff --git a/src/js/node/zlib.ts b/src/js/node/zlib.ts index 5949e454ced..76a9c39cd2d 100644 --- a/src/js/node/zlib.ts +++ b/src/js/node/zlib.ts @@ -82,10 +82,11 @@ function zlibBufferOnData(chunk) { if (!this.buffers) this.buffers = [chunk]; else ArrayPrototypePush.$call(this.buffers, chunk); this.nread += chunk.length; - if (this.nread > this._maxOutputLength) { + const maxOutputLength = this._maxOutputLength; + if (this.nread > maxOutputLength) { this.close(); this.removeAllListeners("end"); - this.cb($ERR_BUFFER_TOO_LARGE(this._maxOutputLength)); + this.cb($ERR_BUFFER_TOO_LARGE(maxOutputLength)); } } @@ -360,9 +361,10 @@ function processChunkSync(self, chunk, flushFlag) { ArrayPrototypePush.$call(buffers, out); nread += out.byteLength; - if (nread > self._maxOutputLength) { + const maxOutputLength = self._maxOutputLength; + if (nread > maxOutputLength) { _close(self); - throw $ERR_BUFFER_TOO_LARGE(self._maxOutputLength); + throw $ERR_BUFFER_TOO_LARGE(maxOutputLength); } } else { $assert(have === 0, "have should not go down"); @@ -453,10 +455,12 @@ function processCallback() { } // Exhausted the output buffer, or used all the input create a new one. - if (availOutAfter === 0 || self._outOffset >= self._chunkSize) { - handle.availOutBefore = self._chunkSize; + let chunkSize; + if (availOutAfter === 0 || self._outOffset >= (chunkSize = self._chunkSize)) { + chunkSize ??= self._chunkSize; + handle.availOutBefore = chunkSize; self._outOffset = 0; - self._outBuffer = Buffer.allocUnsafe(self._chunkSize); + self._outBuffer = Buffer.allocUnsafe(chunkSize); } if (availOutAfter === 0) { diff --git a/src/js/thirdparty/node-fetch.ts b/src/js/thirdparty/node-fetch.ts index ac5c33e8fc0..221cd23f8ec 100644 --- a/src/js/thirdparty/node-fetch.ts +++ b/src/js/thirdparty/node-fetch.ts @@ -155,12 +155,13 @@ async function fetch( // Convert Node.js streams to Web ReadableStream if they don't have Symbol.asyncIterator. // This is needed for libraries like `form-data` that use CombinedStream which extends // Node.js Stream but doesn't implement Symbol.asyncIterator. - if (init?.body && typeof init.body === "object" && !init.body[Symbol.asyncIterator]) { + const initBody = init?.body; + if (initBody && typeof initBody === "object" && !initBody[Symbol.asyncIterator]) { const { Readable, Stream, PassThrough } = require("node:stream"); - if (init.body instanceof Stream || init.body instanceof Readable) { + if (initBody instanceof Stream || initBody instanceof Readable) { // For old-style streams that don't have asyncIterator (like CombinedStream used by form-data), // pipe through a PassThrough stream to convert to a Readable that can be converted to a web stream. - let readable = init.body; + let readable = initBody; if (!(readable instanceof Readable)) { const passthrough = new PassThrough(); readable.pipe(passthrough); diff --git a/src/js/thirdparty/ws.js b/src/js/thirdparty/ws.js index 07a8db2edde..25d68a95208 100644 --- a/src/js/thirdparty/ws.js +++ b/src/js/thirdparty/ws.js @@ -31,24 +31,25 @@ function extractAgentOptions(agent) { const newTlsOptions = {}; let hasTlsOptions = false; - if (connectOpts.rejectUnauthorized !== undefined) { - newTlsOptions.rejectUnauthorized = connectOpts.rejectUnauthorized; + const { rejectUnauthorized, ca, cert, key, passphrase } = connectOpts; + if (rejectUnauthorized !== undefined) { + newTlsOptions.rejectUnauthorized = rejectUnauthorized; hasTlsOptions = true; } - if (connectOpts.ca) { - newTlsOptions.ca = connectOpts.ca; + if (ca) { + newTlsOptions.ca = ca; hasTlsOptions = true; } - if (connectOpts.cert) { - newTlsOptions.cert = connectOpts.cert; + if (cert) { + newTlsOptions.cert = cert; hasTlsOptions = true; } - if (connectOpts.key) { - newTlsOptions.key = connectOpts.key; + if (key) { + newTlsOptions.key = key; hasTlsOptions = true; } - if (connectOpts.passphrase) { - newTlsOptions.passphrase = connectOpts.passphrase; + if (passphrase) { + newTlsOptions.passphrase = passphrase; hasTlsOptions = true; } @@ -167,11 +168,12 @@ class BunWebSocket extends EventEmitter { agent = options?.agent; if ($isObject(agent)) { const agentOpts = extractAgentOptions(agent); - if (!proxy && agentOpts.proxy) { - proxy = agentOpts.proxy; + const { proxy: agentProxy, tls: agentTls } = agentOpts; + if (!proxy && agentProxy) { + proxy = agentProxy; } - if (!tlsOptions && agentOpts.tls) { - tlsOptions = agentOpts.tls; + if (!tlsOptions && agentTls) { + tlsOptions = agentTls; } } } @@ -1174,15 +1176,12 @@ class WebSocketServer extends EventEmitter { ...options, }; - if ( - (options.port == null && !options.server && !options.noServer) || - (options.port != null && (options.server || options.noServer)) || - (options.server && options.noServer) - ) { + const { port, server, noServer, host, backlog } = options; + if ((port == null && !server && !noServer) || (port != null && (server || noServer)) || (server && noServer)) { throw new TypeError('One and only one of the "port", "server", or "noServer" options must be specified'); } - if (options.port != null) { + if (port != null) { this._server = http.createServer((req, res) => { const body = http.STATUS_CODES[426]; @@ -1193,12 +1192,13 @@ class WebSocketServer extends EventEmitter { res.end(body); }); - this._server.listen(options.port, options.host, options.backlog, callback); - } else if (options.server) { - this._server = options.server; + this._server.listen(port, host, backlog, callback); + } else if (server) { + this._server = server; } - if (this._server) { + const ownServer = this._server; + if (ownServer) { const emitConnection = this.emit.bind(this, "connection"); const emitListening = this.emit.bind(this, "listening"); const emitError = this.emit.bind(this, "error"); @@ -1206,14 +1206,14 @@ class WebSocketServer extends EventEmitter { this.handleUpgrade(req, socket, head, emitConnection); }; - this._server.on("listening", emitListening); - this._server.on("error", emitError); - this._server.on("upgrade", doUpgrade); + ownServer.on("listening", emitListening); + ownServer.on("error", emitError); + ownServer.on("upgrade", doUpgrade); this._removeListeners = () => { - this._server.removeListener("upgrade", doUpgrade); - this._server.removeListener("listening", emitListening); - this._server.removeListener("error", emitError); + ownServer.removeListener("upgrade", doUpgrade); + ownServer.removeListener("listening", emitListening); + ownServer.removeListener("error", emitError); }; } @@ -1278,8 +1278,9 @@ class WebSocketServer extends EventEmitter { this._removeListeners = this._server = null; } - if (this.clients) { - if (!this.clients.size) { + const clients = this.clients; + if (clients) { + if (!clients.size) { process.nextTick(server => { server._state = CLOSED; server.emit("close"); @@ -1318,11 +1319,12 @@ class WebSocketServer extends EventEmitter { * @public */ shouldHandle(req) { - if (this.options.path) { + const optionsPath = this.options.path; + if (optionsPath) { const index = req.url.indexOf("?"); const pathname = index !== -1 ? req.url.slice(0, index) : req.url; - if (pathname !== this.options.path) return false; + if (pathname !== optionsPath) return false; } return true; @@ -1369,12 +1371,13 @@ class WebSocketServer extends EventEmitter { headers: protocol ? { "sec-websocket-protocol": protocol } : undefined, }) ) { - if (this.clients) { - this.clients.add(ws); + const clients = this.clients; + if (clients) { + clients.add(ws); ws.on("close", () => { - this.clients.delete(ws); + clients.delete(ws); - if (this._shouldEmitClose && !this.clients.size) { + if (this._shouldEmitClose && !clients.size) { process.nextTick(wsEmitClose, this); } }); diff --git a/test/internal/oxlint-plugin-bun.test.ts b/test/internal/oxlint-plugin-bun.test.ts new file mode 100644 index 00000000000..a507acff3c3 --- /dev/null +++ b/test/internal/oxlint-plugin-bun.test.ts @@ -0,0 +1,233 @@ +// Tests for the custom oxlint rules in scripts/oxlint-plugins/bun.js. +// +// The plugin is loaded via `jsPlugins` in oxlint.json and only enabled for +// src/js/** through an override. These tests exercise the rule directly by +// pointing oxlint at fixture files with a minimal config. + +import { describe, expect, test } from "bun:test"; +import { existsSync } from "fs"; +import { bunEnv, bunExe, isASAN, tempDir } from "harness"; +import path from "path"; + +const root = path.resolve(import.meta.dir, "..", ".."); +const pluginPath = path.join(root, "scripts", "oxlint-plugins", "bun.js"); +// Use the pinned oxlint from the repo's devDependencies so the test is +// hermetic (no registry fetch) and version-locked to the jsPlugins API the +// plugin is written against. +const oxlintBin = path.join(root, "node_modules", "oxlint", "bin", "oxlint"); +const RULE = "bun(no-duplicate-conditional-property-access)"; + +// oxlint ships a prebuilt NAPI binding that aborts when loaded under the +// ASAN build; the rule is still enforced in CI by the Lint JavaScript +// workflow (release bun), so skip here. Also skip if the repo's +// devDependencies haven't been installed yet. +const skip = isASAN || !existsSync(oxlintBin); +const describeOxlint = skip ? describe.skip : describe; + +async function runOxlint(files: Record) { + using dir = tempDir("oxlint-plugin-bun", { + ...files, + "oxlint.json": JSON.stringify({ + jsPlugins: [pluginPath], + categories: {}, + rules: { + "bun/no-duplicate-conditional-property-access": "error", + }, + }), + }); + await using proc = Bun.spawn({ + cmd: [bunExe(), oxlintBin, "--config=oxlint.json", "--format=github", "."], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + return { stdout, stderr, exitCode }; +} + +function diagnostics(stdout: string) { + const out: { file: string; line: number; rule: string }[] = []; + for (const m of stdout.matchAll(/::error file=([^,]+),line=(\d+),[^:]*title=([^:]+)::/g)) { + out.push({ file: m[1], line: parseInt(m[2], 10), rule: m[3] }); + } + return out; +} + +describeOxlint("bun/no-duplicate-conditional-property-access", () => { + test("flags re-reading the property inside the if body", async () => { + const { stdout, stderr, exitCode } = await runOxlint({ + "bad.js": ` +let fragment, unicode, search, auth; +if (options.fragment != null) { + fragment = Boolean(options.fragment); +} +if (options.unicode != null) { + unicode = Boolean(options.unicode); +} +if (options.search != null) { + search = Boolean(options.search); +} +if (options.auth != null) { + auth = Boolean(options.auth); +} +// without braces +if (options.x !== undefined) x = options.x; +// null on the left +if (null != options.y) y = options.y; +// truthy check +if (options.cert) throwIfInvalid("cert", options.cert); +// numeric comparison +if (parser.maxHeaderPairs > 0) n = Math.min(n, parser.maxHeaderPairs); +// typeof check +if (typeof options.enc === "string") use(options.enc); +// multi-statement body +if (options.port != null) { + server.listen(options.port, options.host); + started = true; +} +// nested property chain +if (this.a.b != null) { + use(this.a.b); +} +`, + }); + + expect(stderr).not.toContain("Failed"); + expect(diagnostics(stdout)).toEqual([ + { file: "bad.js", line: 3, rule: RULE }, + { file: "bad.js", line: 6, rule: RULE }, + { file: "bad.js", line: 9, rule: RULE }, + { file: "bad.js", line: 12, rule: RULE }, + { file: "bad.js", line: 16, rule: RULE }, + { file: "bad.js", line: 18, rule: RULE }, + { file: "bad.js", line: 20, rule: RULE }, + { file: "bad.js", line: 22, rule: RULE }, + { file: "bad.js", line: 24, rule: RULE }, + { file: "bad.js", line: 26, rule: RULE }, + { file: "bad.js", line: 31, rule: RULE }, + ]); + expect(exitCode).toBe(1); + }); + + test("ignores destructured locals, different properties, nested functions, computed access, and method calls", async () => { + const { stdout, stderr, exitCode } = await runOxlint({ + "good.js": ` +const { fragment: fragmentOption } = options; +if (fragmentOption != null) { + fragment = Boolean(fragmentOption); +} +// different property inside the body +if (options.a != null) { + b = options.c; +} +// access is inside a nested function (runs later, different scope) +if (options.cb != null) { + register(() => options.cb()); +} +// computed access cannot be destructured +if (options[key] != null) { + v = options[key]; +} +// optional chaining +if (a?.b != null) { + use(a?.b); +} +// condition reads the property, body only calls it as a method: +// caching in a local would lose the receiver. +if (obj.handler) { + obj.handler(); +} +// condition calls the property as a method (no cacheable value read) +if (obj.check()) { + use(obj.check); +} +// inline-assignment in the condition is the recommended fix; a +// short-circuit fallback read in the body preserves the original +// access timing and should not be flagged. +let prop; +if (other || (prop = obj.prop)) { + use(prop ?? obj.prop); +} +`, + }); + + expect(stderr).not.toContain("Failed"); + expect(diagnostics(stdout)).toEqual([]); + expect(exitCode).toBe(0); + }); + + test("ignores bodies that write to the same property", async () => { + const { stdout, stderr, exitCode } = await runOxlint({ + "writes.js": ` +// simple assignment to the property: caching would change semantics +if (obj.x != null) { + use(obj.x); + obj.x = null; +} +// compound assignment +if (self.pos !== undefined) { + self.pos += n; +} +// update expression +if (self.count !== undefined) { + self.count++; +} +// delete: not a [[Get]], and a cached local cannot replace the delete +if (obj.y != null) { + delete obj.y; +} +// pure read with no write-back: still flagged (positive control) +if (map.entry != null) { + entries.push(map.entry); +} +`, + }); + + expect(stderr).not.toContain("Failed"); + // Only the last case (a pure read with no write-back) should fire. + expect(diagnostics(stdout)).toEqual([{ file: "writes.js", line: 20, rule: RULE }]); + expect(exitCode).toBe(1); + }); + + test("inline disable comment suppresses the diagnostic", async () => { + const { stdout, stderr, exitCode } = await runOxlint({ + "suppressed.js": ` +// oxlint-disable-next-line bun/no-duplicate-conditional-property-access +if (options.a != null) x = options.a; +if (options.b != null) y = options.b; +`, + }); + + expect(stderr).not.toContain("Failed"); + expect(diagnostics(stdout)).toEqual([{ file: "suppressed.js", line: 4, rule: RULE }]); + expect(exitCode).toBe(1); + }); + + test("diagnostic message suggests destructuring the base object", async () => { + const { stdout, exitCode } = await runOxlint({ + "msg.js": `if (options.fragment != null) { x = options.fragment; }\n`, + }); + expect(stdout).toContain("`options.fragment` is read in the `if` condition and again in the body"); + expect(stdout).toContain("const { fragment } = options"); + expect(exitCode).toBe(1); + }); +}); + +describeOxlint("src/js lint", () => { + // End-to-end: the repo's own oxlint config, against src/js, should be + // clean. Existing instances of the pattern were refactored to read the + // property into a local before the check; this guards against new ones. + test("bun run lint is clean on src/js", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), oxlintBin, "--config=oxlint.json", "--format=github", "src/js"], + cwd: root, + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + expect(stderr).not.toContain("Failed"); + expect({ stdout, exitCode }).toEqual({ stdout: expect.stringContaining("0 errors"), exitCode: 0 }); + }); +});