JavaScript leaks through more channels than most languages. Identifiers, sure, but also JSX copy that quotes your UI word for word, template literals that assemble your API routes, and the CRM hostname sitting in a fetch call.

What a snippet gives away

Take an ordinary service class heading for a code review by ChatGPT. InvoiceSyncService announces the domain. this.crmUrl plus a template literal spells out the API of an internal system. The Slack message in the error path contains your incident wording and a ticket prefix. The comment on top names the customer. None of that is needed to answer the actual question, which is about a loop that swallows errors.

After the pass above, the same file shows a Class1 with a fn2 doing a fetch in a loop with an unawaited edge case. The question got sharper and the company left the room.

The round trip

Mode one replaces every identifier you own with a role-named placeholder, Class1, fn2, var3, param4, prop5, mod6 for module paths, CONST_7 for screaming-case constants. String literals and JSX text become str_1 style placeholders, comments disappear, and the layout survives byte for byte. Mode two, restore names, takes whatever came back, your placeholders in whatever new arrangement the reviewer produced, and swaps the originals in again.

Between the two sits the key, a browser-local mapping that grows with every paste and can be exported as JSON. Nothing about the code or the key leaves the tab, the processing is a bundled script, not an API call.

Template literals and JSX

The two places naive tooling falls over are the two syntaxes where strings and code interleave.

IDE rename: strings and JSX text keep leaking
await fetch(`${this.crmUrl}/invoices/${inv.id}`)
notify(`sync failed for ${inv.number} (acme-${inv.id})`)
return <p>Overdue invoices for {customer.company}</p>
tokenized: text masked, expressions still code
await fetch(`${this.prop1}/str_1/${var2.prop3}`)
fn4(`str_2 ${var2.prop5} (str_3-${var2.prop3})`)
return <p>str_4 {var5.prop6}</p>

An IDE rename fixes identifiers and leaves every literal piece in place, so the route fragment, the failure wording and the on-screen copy travel anyway. The tokenizer treats each literal piece as data to mask and each embedded expression as code to rename, which is the only split that keeps the result both harmless and readable.

In JSX, lowercase tags and their standard attributes are HTML and stay. Your components and custom props are yours and go. A className="btn btn-primary" survives as CSS rather than data, and regex literals are recognized as regexes instead of being shredded at the slashes.

Imports draw the line

Whether a name is public vocabulary or your vocabulary is decided where it enters the file. Names imported from npm packages and node: builtins stay readable, along with globals, the DOM and the usual framework surface. Names imported from a relative path are project code, the path becomes mod1 and its exports get placeholders. require() calls follow the same rule.

One line of nuance: scoped packages are matched against a list of known organizations, so an unrecognized @yourcompany/billing-core is treated as yours and replaced, which is exactly where private registries belong.

Minified is not anonymous

A tempting shortcut is to run the snippet through a minifier and share that. It fails on both ends. The literals all stay, endpoints, messages, keys in object literals, because a minifier must preserve behavior. And the mangled control flow makes the reviewer's job harder rather than easier. Anonymizing optimizes for the opposite corner: everything that reads as code stays readable, everything that reads as yours goes, and a key brings it back.

The related trap is sharing a bundle and assuming build tooling scrubbed it. It did not.

Restore survives edits

Restore mode re-tokenizes the pasted answer, so placeholders are recognized wherever they ended up, in new functions, in template literals, in JSX the reviewer added. Placeholders the key never issued are listed below the output instead of being guessed at, and a placeholder embedded in a longer identifier such as fn2Cached is left alone, preserving the reviewer's naming for genuinely new things. Placeholders are matched exactly, including case, since JavaScript names are case-sensitive.

The practical effect: you can accept a model's whole rewritten file, restore it, and diff it against your original in the editor.

Pasting frontend code into ChatGPT, answered

Is it safe to post company code on Stack Overflow?

The post is public, indexed and effectively permanent, so the question is really whether your employer would publish that snippet on its own blog. Usually the answer is no because of what the names and strings reveal, not the logic. A version with neutral identifiers and masked strings changes that calculation, and it is also simply a better question, since readers focus on the problem instead of the domain.

How do I anonymize JavaScript before giving it to an AI tool?

Rename your identifiers to placeholders, mask string literals and JSX text, drop comments, and record the mapping. The tool above does that in one pass as you paste, TypeScript and JSX included, and the mapping stays in your browser as a key for restoring the answer.

Is minified JavaScript anonymous?

No. Minification shortens local variables but keeps every property name, API route, error message and string literal intact, because renaming those would break the program. A production bundle still contains your endpoints and your wording.

Do source maps expose original code?

Fully, that is their job. A deployed .map file hands any visitor your original sources, comments included. Keep them off public servers.

What is the difference between this and a JavaScript obfuscator?

An obfuscator armors code against the person receiving it, with string encryption and control-flow flattening, and accepts becoming unreadable. An anonymizer does the opposite, it keeps the code readable for the recipient and removes only the link to your project, with a key to undo it. Obfuscated code gets worse answers from both humans and models.

Can I share a React component without revealing the product?

Yes, if the component names, prop names, JSX text and CSS-unrelated strings go. Hooks, HTML tags and library imports can stay, they are public vocabulary. That split is exactly what this page automates, visible copy included, since the marketing text inside your JSX identifies the product faster than any variable name.

Does TypeScript still type-check after renaming?

Consistent renaming preserves the type structure. Interfaces, generics and their uses are replaced as one name each, so relationships hold.

Should library calls be anonymized too?

No. useState, fetch and express are the same in every codebase on earth, hiding them protects nothing and destroys the answer, because the recipient no longer sees which APIs are in play. Only your own vocabulary carries information about you.

Can ChatGPT still find a bug in code full of placeholders?

The bugs a reviewer finds live in control flow, async handling, state updates and API misuse, all of which survive renaming untouched. What gets lost is naming-level advice, a function whose behavior contradicts its name cannot be flagged anymore.