diff --git a/scripts/loopback-lint-selftest.mjs b/scripts/loopback-lint-selftest.mjs index b5bf21061..7d6a5ec98 100644 --- a/scripts/loopback-lint-selftest.mjs +++ b/scripts/loopback-lint-selftest.mjs @@ -56,6 +56,9 @@ const EXT_TARGET = 'vscode-ext/vitest.smoketest.config.mts'; */ const FIXTURES = [ ['node, positional', "\nexport function __selftest(s) { s.listen(9999, '127.0.0.1'); }\n"], + // A port computed by a call — `Number(process.env.PORT || 0)` — whose inner + // `)` ends a scan that stops at the first one. + ['node, positional', "\nexport function __selftest(s) { s.listen(Number(process.env.PORT || 0), '127.0.0.1'); }\n"], ['node, options object', "\nexport function __selftest(s) { s.listen({ port: 9999, host: '127.0.0.1' }); }\n"], ['@hono/node-server', "\nexport function __selftest(app) { serve({ fetch: app.fetch, port: 9999, hostname: '127.0.0.1' }); }\n"], ['ws, explicit loopback host', "\nexport function __selftest() { return new WebSocketServer({ host: '127.0.0.1' }); }\n"], diff --git a/scripts/loopback-lint.mjs b/scripts/loopback-lint.mjs index 8a8f4268c..89864f55b 100644 --- a/scripts/loopback-lint.mjs +++ b/scripts/loopback-lint.mjs @@ -36,6 +36,8 @@ * options nest again (`headers`, `cookieDomainRewrite`), and `configure` * takes a function whose body carries braces of its own. A `host` written * below one of those is a miss, and the audit is what covers it. + * - A positional port may wrap one call (`Number(process.env.PORT || 0)`); + * a port expression nesting parentheses deeper hides the bind. * - Outside `ws`, it matches only an explicit loopback host. A listener that * binds every interface (`.listen(port)` with no host) is a different and * larger problem, and `relay/` does it deliberately from config, so @@ -127,7 +129,7 @@ const NESTED_KEYS = '(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*?'; * exercises is a claim, not a check. */ const BIND_FORMS = [ - { label: 'node, positional', re: `\\.listen\\(\\s*[^,)]+,\\s*${LOOPBACK}` }, + { label: 'node, positional', re: `\\.listen\\(\\s*(?:[^,()]|\\([^()]*\\))+,\\s*${LOOPBACK}` }, { label: 'node, options object', re: `\\.listen\\(\\s*\\{[^}]*?host\\s*:\\s*${LOOPBACK}` }, { label: '@hono/node-server', re: `\\bserve\\(\\s*\\{[^}]*?hostname\\s*:\\s*${LOOPBACK}` }, { label: 'ws, explicit loopback host', re: `${WS_NEW}host\\s*:\\s*${LOOPBACK}` },