Skip to content

feat!: report what a text should be refused for, move to TypeScript, ESM, vite and vitest - #161

Merged
albanv merged 4 commits into
masterfrom
feat/strict-matches
Nov 12, 2026
Merged

albanv merged 4 commits into
masterfrom
feat/strict-matches

Conversation

@albanv

@albanv albanv commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Four commits: the feature, the move of the package to our current tooling, the update of its dependencies, and a typing fix found while plugging a TypeScript caller onto the built package (the matches of forbidden are typed link or phone, never email).

1. Report what a text should be refused for

Why

censorify-it finds links, emails and phone numbers to censor them. We also want to refuse a text that holds a link or a phone number, at the time it is written. The same package should answer both, so that what is refused and what is censored do not drift apart.

The existing detection cannot be reused as is for refusing. It is tuned to censor, where a false positive only costs a few stars:

Text Found today If we refused on it
evil.cfd/a/x (no scheme, unknown TLD) nothing the link goes through
see you soon.In any case soon.In, as a link an honest text is refused
group of 12 15 20 25 people 12 15 20 25, as a phone number an honest text is refused

What

  • forbidden(text) lists the links and phone numbers a text should be refused for, exceptions applied. It relies on a strict detection, kept apart from the one used to censor:
    • links: scheme://, www., domain/path or domain?query, whatever the TLD. A bare domain is not reported;
    • phone numbers: international ones from 8 digits, national ones from 9 digits. Dates and lists of small numbers are left aside.
  • Every match has a type: link, email or phone.
  • Exceptions can be given by type: set({ exceptions: { link: [...], phone: [...] } }). A single list still works. By type is safer: a loose phone number pattern in a single list also lets through the links holding these digits.
  • Censoring gains one case: a link written without scheme on an unknown TLD is now censored, and a strict link wins over the loose match it holds (evil.cfd?mysite.com/x is one link to evil.cfd). The censoring of phone numbers and emails is unchanged: the six existing tests pass untouched.

Everything forbidden reports is censored by process as well; a test holds that property.

Known limits of the strict detection

  • Not seen: 8-digit national numbers written without + (they look like a date), numbers spelled out or split with slashes, obfuscated links (evil . com).
  • Wrongly seen: a few runs of 9 digits or more that are not phone numbers, such as a reference or 150.00-200.00.
  • No lookbehind is used, for old Safari.

2. TypeScript, ES modules, vite and vitest

  • TypeScript: the source is a class extending LinkifyIt, and the package ships its types (CensorifyIt, Match, Options, Exceptions...).
  • ES module, built with vite, with a CommonJS build: dist/censorify-it.js and dist/censorify-it.cjs. The class is the default export and a named one. Until now a default import under Node ESM gave the module object, and new CensorifyIt() threw.
  • vitest replaces jest, with coverage (98.8 % of statements).
  • Lint and hooks as in our other packages: flat config of eslint-config-vizeat, husky, lint-staged and commitlint. babel is gone.
  • CI rewritten: the previous workflow relied on deprecated actions and failed before running anything. The new one lints, checks types, builds, loads the built package both ways (import and require), and tests with coverage, on Node 24.
  • Dependencies updated. linkify-it goes from 3 to 5: over 26,000 sample texts, the only change in what is censored is a trailing punctuation mark or closing bracket that is no longer swallowed. It does not go to 6, which no longer finds an email written right after a label (email:john@example.com) nor a domain with a port (evil.com:8080); a test holds these two cases, so the Renovate PR for v6 will fail. TypeScript goes to 6, not 7 (vite-plugin-dts needs the JavaScript compiler API), vitest to 5, commitlint to 21, lint-staged to 17.

Breaking changes

Version 4.0.0:

  • the package is an ES module with an exports map: its files moved, and deep imports are no longer possible;
  • require('censorify-it').default still gives the class.

The API is otherwise only extended.

Tests

51 tests pass (6 existing, 45 new), lint and type check pass. The built package was loaded both ways under Node, and the test suite of a TypeScript ESM project depending on it passes against the local build.

To check on this repository

  • CODECOV_TOKEN must exist in the repository secrets for the coverage upload.
  • Publishing is unchanged: yarn pub, to the public npm registry.

🤖 Generated with Claude Code

@albanv albanv self-assigned this Nov 12, 2026
@albanv
albanv requested a review from mm22dev November 12, 2026 11:02
albanv and others added 2 commits November 12, 2026 12:15
- `forbidden(text)` lists the links and phone numbers a text should be refused
  for. It relies on a strict detection, apart from the one used to censor: a
  false positive there rejects an honest text instead of adding a few stars.
- every match gets a `type` (link, email, phone), and exceptions can be given
  by type.
- a link written without scheme on an unknown TLD (evil.cfd/a/x) is now
  censored too; the censoring of phone numbers is unchanged.
- the package gets an ESM entry, so that a default import gives the class
  under Node ESM. `require('censorify-it').default` still works.

BREAKING CHANGE: the package now has an `exports` map, deep imports of its
files are no longer possible.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The source becomes a TypeScript class extending LinkifyIt, built with vite as
an ES module with a CommonJS build and its types. Tests run with vitest, lint
with the flat config of eslint-config-vizeat, and commits go through husky,
lint-staged and commitlint, as in our other packages.

The CI workflow is rewritten: the previous one relied on deprecated actions
and failed before running anything. It now lints, checks types, builds, loads
the package both ways, and tests with coverage on Node 24.

linkify-it stays on v3: v6 changes its defaults (links without a scheme are
no longer found), which would change what is censored.

BREAKING CHANGE: the package is an ES module with an `exports` map. Its files
moved to dist/censorify-it.js and dist/censorify-it.cjs, and deep imports are
no longer possible. `require('censorify-it').default` still gives the class.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@albanv
albanv force-pushed the feat/strict-matches branch from 71bb426 to a985400 Compare November 12, 2026 11:15
@albanv albanv changed the title feat: report what a text should be refused for, type the matches feat!: report what a text should be refused for, move to TypeScript, ESM, vite and vitest Nov 12, 2026
@codecov-commenter

codecov-commenter commented Nov 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (ee20392) to head (114be58).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #161   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines           50        70   +20     
  Branches        11        22   +11     
=========================================
+ Hits            50        70   +20     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

albanv and others added 2 commits November 12, 2026 12:25
`forbidden` and `matchStrict` never report an email: their matches are now
typed 'link' | 'phone', so a caller branching on the type does not have to
handle a case that cannot happen.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
linkify-it goes from 3 to 5. What is censored does not change, except for a
trailing punctuation mark or closing bracket that is no longer swallowed.

It does not go to 6, which no longer finds an email written right after a
label ("email:john@example.com") nor a domain with a port ("evil.com:8080").
A test now holds these two cases.

TypeScript goes to 6, not 7: vite-plugin-dts needs the JavaScript compiler
API. vitest goes to 5, commitlint to 21, lint-staged to 17.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Comment thread package.json Resolved
@albanv
albanv merged commit 8f5a621 into master Nov 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants