fix(js): no call resolves by name into a minified bundle (#2204)

A vendored minified script (`*.min.js`, or a script of a few enormous lines
- the same detection #2122 uses to rank them last) keeps mangled one-letter
names, so a name match from any other file is coincidence: healthchecks'
369 `$(...)` jQuery calls went to a helper inside bootstrap-native.min.js,
TestBox's popper.min.js to jquery.min.js internals, flaskbb's docs script
to its vendors bundle. Such files' symbols are no longer visible across
files by name; calls inside a bundle still resolve.

healthchecks -460, flaskbb -48, TestBox -36; bat, lodash, minimal-mistakes,
netbox, express, excalidraw unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-30 21:09:20 +00:00
committed by GitHub
parent 4c381aedbd
commit e7be363f95
3 changed files with 68 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes
- Calls in your own JavaScript no longer link to a same-named function inside a vendored minified script, like a `*.min.js` or a bundle made of a few enormous lines. Those names are mangled. In healthchecks, every jQuery `$(…)` call had gone to a one-letter helper inside `bootstrap-native.min.js`.
- A Swift call on a value built on the spot, like `JSONDecoder().decode(…)` or `Realm().create(…)`, now resolves to that type's method, or to one it inherits. It no longer lands on some other type's method of the same name: Vapor's `JSONDecoder().decode` calls had gone to a request's private container.
- A bare Lua call like `ipairs(t)`, `error("…")` or busted's `setup()` now means Lua's own function unless the file defines one. It no longer lands on a table method of the same name: telescope's `ipairs` calls had gone to a linked list's `ipairs`, and kong's `error` calls to its response module. A function's own alias of a host table member, like `local get_query = kong.request.get_query`, is now followed as well.
- A C++ call into a macro-opened namespace, like `fmt::format("{}", x)`, now picks the overload its arguments fit. Before, the first overload indexed won: fmt's 1,400 `fmt::format` calls all reached the `text_style` overload in `color.h`. A narrow string literal now fits a narrow format string, a wide `L"…"` fits the wide overload, and the argument count has to fit too.
+47
View File
@@ -0,0 +1,47 @@
/**
* A vendored minified script's names are mangled, so no other file's call
* means one of them by name: healthchecks' 369 `$(…)` calls — jQuery, loaded
* as a global — went to a one-letter helper inside bootstrap-native.min.js.
* Detected by name (`*.min.js`) or by text (a few enormous lines).
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-minified-'));
const bundle = `!function(){${Array.from({ length: 120 }, (_, i) => `function f${i}(e,t){return e+t+${i}}`).join(';')};function u(e){return f1(e,2)}}();\n`;
const files: Record<string, string> = {
'static/js/vendor.min.js': `function $(e){return document.querySelector(e)}function q(e){return $(e)}\n`,
'static/js/bundle.js': bundle.repeat(4),
'static/js/app.js': `function init() {
$("#edit-name").click(function () {});
u("x");
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('calls into minified scripts', () => {
it('never resolve by name from another file', () => {
const ids = cg.getNodesInFile('static/js/app.js').map((n) => n.id);
const targets = cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => cg.getNode(e.target)!.filePath);
expect(targets).not.toContain('static/js/vendor.min.js');
expect(targets).not.toContain('static/js/bundle.js');
});
});
+20
View File
@@ -12,6 +12,7 @@ import { blankStringContents, stripCommentsForRegex } from './strip-comments';
import { JS_BUILT_INS, JS_BUILTIN_METHODS, TS_PRIMITIVE_TYPES } from './js-builtins';
import { SWIFT_TYPE_PATH_CALL, resolveSwiftTypePathCall } from './swift-type-visibility';
import { isTestPath } from '../search/query-utils';
import { isMinifiedContent } from '../extraction/generated-detection';
/**
* Ceiling on how many same-named definitions a FUZZY name-match strategy will
* score. A name defined more times than this is "ubiquitous" — a method/symbol
@@ -932,6 +933,21 @@ function isLuaLocal(candidate: Node, context: ResolutionContext): boolean {
const JVM_CALLABLE_KINDS: ReadonlySet<string> = new Set(['method', 'function']);
const JVM_TYPE_KINDS: ReadonlySet<string> = new Set(['class', 'interface', 'enum', 'struct', 'trait', 'type_alias', 'annotation']);
const MINIFIED_SCRIPTS = new WeakMap<ResolutionContext, Map<string, boolean>>();
/** A minified / bundled script, by name (`jquery.min.js`) or by its text. */
function isMinifiedScript(filePath: string, context: ResolutionContext): boolean {
if (!/\.(?:m?js|cjs)$/i.test(filePath)) return false;
let memo = MINIFIED_SCRIPTS.get(context);
if (!memo) MINIFIED_SCRIPTS.set(context, (memo = new Map()));
let hit = memo.get(filePath);
if (hit === undefined) {
hit = /[.-]min\.m?js$/i.test(filePath) || isMinifiedContent(filePath, context.readFile(filePath) ?? '');
memo.set(filePath, hit);
}
return hit;
}
/** Per context: every package the project's JVM sources declare. */
const JVM_PACKAGES = new WeakMap<ResolutionContext, Set<string>>();
@@ -1276,6 +1292,9 @@ function rustModuleDir(filePath: string): string {
*/
export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
if (candidate.filePath === ref.filePath) return true;
// A vendored minified bundle's names are mangled: healthchecks' 369 `$(…)`
// (jQuery, a global) went to a one-letter helper inside bootstrap-native.min.js.
if (isMinifiedScript(candidate.filePath, context)) return false;
const lang = candidate.language as string;
if (lang === 'c' || lang === 'cpp') {
return (
@@ -5095,6 +5114,7 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
LUA_LOCALS.delete(context);
LUA_MEMBERS.delete(context);
JVM_PACKAGES.delete(context);
MINIFIED_SCRIPTS.delete(context);
PY_LOCAL_BINDS.delete(context);
PHP_FILE_SCOPES.delete(context);
JAVA_STATIC_IMPORTS.delete(context);